what-server 0.6.3 → 0.6.6
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.js +50 -4
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +8 -8
- package/dist/index.min.js.map +3 -3
- package/dist/islands.js +4 -1
- package/dist/islands.js.map +2 -2
- package/dist/islands.min.js +1 -1
- package/dist/islands.min.js.map +3 -3
- package/package.json +2 -2
- package/src/index.js +59 -4
- package/src/islands.js +10 -1
package/dist/index.js
CHANGED
|
@@ -633,23 +633,69 @@ function renderAttrs(props) {
|
|
|
633
633
|
if (key === "key" || key === "ref" || key === "children" || key === "dangerouslySetInnerHTML" || key === "innerHTML") continue;
|
|
634
634
|
if (key.startsWith("on") && key.length > 2) continue;
|
|
635
635
|
if (val === false || val == null) continue;
|
|
636
|
+
const attrName = getHtmlAttributeName(key);
|
|
637
|
+
if (!isValidHtmlAttributeName(attrName)) {
|
|
638
|
+
if (_isDevMode) console.warn(`[what-server] Omitted invalid attribute name: ${key}`);
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (!isSafeUrlAttributeValue(attrName, val)) {
|
|
642
|
+
if (_isDevMode) console.warn(`[what-server] Omitted unsafe URL attribute "${attrName}": ${val}`);
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
636
645
|
if (key === "className" || key === "class") {
|
|
637
646
|
out += ` class="${escapeHtml(String(val))}"`;
|
|
638
647
|
} else if (key === "style" && typeof val === "object") {
|
|
639
648
|
const css = Object.entries(val).map(([p, v]) => `${camelToKebab(p)}:${v}`).join(";");
|
|
640
649
|
out += ` style="${escapeHtml(css)}"`;
|
|
641
650
|
} else if (val === true) {
|
|
642
|
-
if (
|
|
643
|
-
out += ` ${
|
|
651
|
+
if (attrName.startsWith("aria-") || attrName === "role") {
|
|
652
|
+
out += ` ${attrName}="true"`;
|
|
644
653
|
} else {
|
|
645
|
-
out += ` ${
|
|
654
|
+
out += ` ${attrName}`;
|
|
646
655
|
}
|
|
647
656
|
} else {
|
|
648
|
-
out += ` ${
|
|
657
|
+
out += ` ${attrName}="${escapeHtml(String(val))}"`;
|
|
649
658
|
}
|
|
650
659
|
}
|
|
651
660
|
return out;
|
|
652
661
|
}
|
|
662
|
+
function getHtmlAttributeName(name) {
|
|
663
|
+
if (name === "className") return "class";
|
|
664
|
+
if (name === "htmlFor") return "for";
|
|
665
|
+
return name;
|
|
666
|
+
}
|
|
667
|
+
function isValidHtmlAttributeName(name) {
|
|
668
|
+
return /^[^\s"'>/=\x00-\x1f\x7f]+$/.test(name);
|
|
669
|
+
}
|
|
670
|
+
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
671
|
+
"href",
|
|
672
|
+
"src",
|
|
673
|
+
"action",
|
|
674
|
+
"formaction",
|
|
675
|
+
"poster",
|
|
676
|
+
"cite",
|
|
677
|
+
"background",
|
|
678
|
+
"xlink:href"
|
|
679
|
+
]);
|
|
680
|
+
var URL_LIST_ATTRS = /* @__PURE__ */ new Set(["srcset"]);
|
|
681
|
+
function isSafeUrlAttributeValue(name, value) {
|
|
682
|
+
const normalizedName = String(name).toLowerCase();
|
|
683
|
+
if (URL_LIST_ATTRS.has(normalizedName)) return isSafeSrcsetValue(value);
|
|
684
|
+
if (URL_ATTRS.has(normalizedName)) return isSafeUrlValue(value);
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
function isSafeUrlValue(value) {
|
|
688
|
+
if (typeof value !== "string") return true;
|
|
689
|
+
const normalized = value.trim().replace(/[\s\x00-\x1f\x7f]/g, "").toLowerCase();
|
|
690
|
+
return !(normalized.startsWith("javascript:") || normalized.startsWith("data:") || normalized.startsWith("vbscript:"));
|
|
691
|
+
}
|
|
692
|
+
function isSafeSrcsetValue(value) {
|
|
693
|
+
if (typeof value !== "string") return true;
|
|
694
|
+
return value.split(",").every((candidate) => {
|
|
695
|
+
const url = candidate.trim().split(/\s+/, 1)[0] || "";
|
|
696
|
+
return url === "" || isSafeUrlValue(url);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
653
699
|
function escapeHtml(str) {
|
|
654
700
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
655
701
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.js", "../src/actions.js"],
|
|
4
|
-
"sourcesContent": ["// What Framework - Server\n// SSR, static site generation, server components.\n// Zero-JS pages by default. Islands opt-in to client JS.\n\nimport { h } from 'what-core';\n\n// --- SSR Error Collection ---\n// Errors that occur during SSR are collected and serialized into the HTML output\n// so the client can pick them up during hydration and display/report them.\n\nlet _ssrErrors = [];\nconst MAX_SSR_ERRORS = 50;\n\nfunction _collectSSRError(error, context = {}) {\n const entry = {\n code: error.code || 'ERR_SSR_RENDER',\n message: error.message || String(error),\n component: context.component || null,\n timestamp: Date.now(),\n };\n // In dev mode, include extra detail for debugging\n if (_isDevMode) {\n entry.suggestion = error.suggestion || null;\n entry.stack = error.stack?.split('\\n').slice(0, 5).join('\\n') || null;\n }\n _ssrErrors.push(entry);\n if (_ssrErrors.length > MAX_SSR_ERRORS) _ssrErrors.shift();\n}\n\nfunction _resetSSRErrors() {\n _ssrErrors = [];\n}\n\n/**\n * Serialize collected SSR errors into a script tag for client hydration.\n * In dev mode: includes full error details (message, suggestion, stack).\n * In production: includes only error code and component name.\n */\nexport function serializeSSRErrors() {\n if (_ssrErrors.length === 0) return '';\n const payload = _isDevMode\n ? _ssrErrors\n : _ssrErrors.map(e => ({ code: e.code, component: e.component }));\n const json = JSON.stringify(payload).replace(/<\\//g, '<\\\\/'); // prevent XSS via </script>\n return `<script type=\"application/json\" data-what-ssr-errors>${json}</script>`;\n}\n\n/**\n * Read SSR errors from the DOM during client hydration.\n * Call this on the client side during hydration to pick up errors from SSR.\n * Returns an array of error objects, or empty array if none.\n */\nexport function hydrateSSRErrors() {\n if (typeof document === 'undefined') return [];\n const el = document.querySelector('script[data-what-ssr-errors]');\n if (!el) return [];\n try {\n const errors = JSON.parse(el.textContent);\n el.remove(); // clean up after reading\n return errors;\n } catch {\n return [];\n }\n}\n\n/**\n * Get collected SSR errors (for programmatic access before serialization).\n */\nexport function getSSRErrors() {\n return _ssrErrors.slice();\n}\n\n// --- Hydration ID Generator ---\nlet _hydrationIdCounter = 0;\n\nfunction resetHydrationId() {\n _hydrationIdCounter = 0;\n}\n\nfunction nextHydrationId() {\n return 'h' + (_hydrationIdCounter++);\n}\n\n// --- Render to Hydratable String ---\n// Renders with hydration markers (data-hk attributes, comment boundaries)\n// so the client can reuse the server-rendered DOM.\n\nexport function renderToHydratableString(vnode) {\n resetHydrationId();\n _resetSSRErrors();\n return _renderHydratable(vnode);\n}\n\nfunction _renderHydratable(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap\n if (typeof vnode === 'function' && vnode._signal) {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n }\n\n // Reactive function child \u2014 wrap in dynamic content markers\n if (typeof vnode === 'function') {\n try {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '<!--$--><!--/$-->';\n }\n }\n\n // Array \u2014 wrap in list markers\n if (Array.isArray(vnode)) {\n return `<!--[]-->${vnode.map(_renderHydratable).join('')}<!--/[]-->`;\n }\n\n // Component \u2014 add hydration key to root element\n if (typeof vnode.tag === 'function') {\n const hkId = nextHydrationId();\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n const html = _renderHydratable(result);\n // Inject data-hk into the first element tag if present\n return injectHydrationKey(html, hkId);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!--ssr-error:${escapeHtml(componentName)}-->`;\n }\n return `<!--ssr-error-->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(_renderHydratable).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// Inject data-hk=\"id\" into the first HTML opening tag\nfunction injectHydrationKey(html, hkId) {\n // Skip comment markers to find the first real element\n const match = html.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);\n if (match) {\n const prefix = match[1];\n const tagName = match[2];\n const insertAt = prefix.length + 1 + tagName.length; // after '<tagName'\n return html.slice(0, insertAt) + ` data-hk=\"${hkId}\"` + html.slice(insertAt);\n }\n return html;\n}\n\n// --- Render to String ---\n// Renders a VNode tree to an HTML string. Used for SSR and static gen.\n\nexport function renderToString(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n return renderToString(vnode());\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n return renderToString(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '';\n }\n }\n\n // Array\n if (Array.isArray(vnode)) {\n return vnode.map(renderToString).join('');\n }\n\n // Component\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n return renderToString(result);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message)} -->`;\n }\n return `<!-- SSR Error -->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(renderToString).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// --- Stream Render ---\n// Returns an async iterator for streaming SSR.\n\nexport async function* renderToStream(vnode) {\n if (vnode == null || vnode === false || vnode === true) return;\n\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n yield escapeHtml(String(vnode));\n return;\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n yield* renderToStream(vnode());\n return;\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n yield* renderToStream(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in stream SSR:', e.message);\n }\n }\n return;\n }\n\n if (Array.isArray(vnode)) {\n for (const child of vnode) {\n yield* renderToStream(child);\n }\n return;\n }\n\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n // Support async components\n const resolved = result instanceof Promise ? await result : result;\n yield* renderToStream(resolved);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in stream SSR:`, e.message);\n }\n yield _isDevMode\n ? `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message || 'Component error')} -->`\n : `<!-- SSR Error -->`;\n }\n return;\n }\n\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n yield `<${tag}${attrs}>`;\n\n if (!VOID_ELEMENTS.has(tag)) {\n const rawInner = _resolveInnerHTML(props);\n if (rawInner != null) {\n yield String(rawInner);\n } else {\n for (const child of children) {\n yield* renderToStream(child);\n }\n }\n yield `</${tag}>`;\n }\n}\n\n// --- Static Site Generation ---\n\nexport function definePage(config) {\n return {\n // 'static' = pre-render at build time (default)\n // 'server' = render on each request\n // 'client' = render in browser (SPA)\n // 'hybrid' = static shell + islands\n mode: 'static',\n ...config,\n };\n}\n\n// Generate static HTML for a page\nexport function generateStaticPage(page, data = {}) {\n _resetSSRErrors();\n const vnode = page.component(data);\n const html = renderToString(vnode);\n const islands = page.islands || [];\n\n return wrapDocument({\n title: page.title || '',\n meta: page.meta || {},\n body: html,\n islands,\n scripts: page.mode === 'static' ? [] : page.scripts || [],\n styles: page.styles || [],\n mode: page.mode,\n ssrErrors: serializeSSRErrors(),\n });\n}\n\nfunction wrapDocument({ title, meta, body, islands, scripts, styles, mode, ssrErrors = '' }) {\n const metaTags = Object.entries(meta)\n .map(([name, content]) => `<meta name=\"${escapeHtml(name)}\" content=\"${escapeHtml(content)}\">`)\n .join('\\n ');\n\n const styleTags = styles\n .map(href => `<link rel=\"stylesheet\" href=\"${escapeHtml(href)}\">`)\n .join('\\n ');\n\n const islandScript = islands.length > 0 ? `\n <script type=\"module\">\n import { hydrateIslands } from '/@what/islands.js';\n hydrateIslands();\n </script>` : '';\n\n const scriptTags = scripts\n .map(src => `<script type=\"module\" src=\"${escapeHtml(src)}\"></script>`)\n .join('\\n ');\n\n const clientScript = mode === 'client' ? `\n <script type=\"module\" src=\"/@what/client.js\"></script>` : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n ${metaTags}\n <title>${escapeHtml(title)}</title>\n ${styleTags}\n </head>\n <body>\n <div id=\"app\">${body}</div>\n ${ssrErrors}\n ${islandScript}\n ${scriptTags}\n ${clientScript}\n </body>\n</html>`;\n}\n\n// --- Server Component ---\n// Renders on the server, sends HTML to client. No JS shipped.\n\nexport function server(Component) {\n Component._server = true;\n return Component;\n}\n\n// --- Helpers ---\n\n// Dev-mode flag for server\nconst _isDevMode = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n/**\n * Resolve innerHTML / dangerouslySetInnerHTML from props.\n * Requires { __html: ... } wrapper. Plain string innerHTML is rejected (XSS prevention).\n */\nfunction _resolveInnerHTML(props) {\n if (!props) return null;\n\n // dangerouslySetInnerHTML always requires { __html }\n if (props.dangerouslySetInnerHTML) {\n return props.dangerouslySetInnerHTML.__html ?? null;\n }\n\n // innerHTML with { __html } wrapper \u2014 allowed\n if (props.innerHTML && typeof props.innerHTML === 'object' && '__html' in props.innerHTML) {\n return props.innerHTML.__html ?? null;\n }\n\n // innerHTML as plain string \u2014 reject with warning\n if (props.innerHTML != null && typeof props.innerHTML === 'string') {\n if (_isDevMode) {\n console.warn(\n '[what-server] innerHTML received a raw string. This is a security risk (XSS). ' +\n 'Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead.'\n );\n }\n return null;\n }\n\n return null;\n}\n\nfunction renderAttrs(props) {\n let out = '';\n for (const [key, val] of Object.entries(props)) {\n if (key === 'key' || key === 'ref' || key === 'children' || key === 'dangerouslySetInnerHTML' || key === 'innerHTML') continue;\n if (key.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR\n if (val === false || val == null) continue;\n\n if (key === 'className' || key === 'class') {\n out += ` class=\"${escapeHtml(String(val))}\"`;\n } else if (key === 'style' && typeof val === 'object') {\n const css = Object.entries(val)\n .map(([p, v]) => `${camelToKebab(p)}:${v}`)\n .join(';');\n out += ` style=\"${escapeHtml(css)}\"`;\n } else if (val === true) {\n // ARIA attributes require explicit =\"true\", HTML boolean attrs can be bare\n if (key.startsWith('aria-') || key === 'role') {\n out += ` ${key}=\"true\"`;\n } else {\n out += ` ${key}`;\n }\n } else {\n out += ` ${key}=\"${escapeHtml(String(val))}\"`;\n }\n }\n\n return out;\n}\n\nfunction escapeHtml(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction camelToKebab(str) {\n if (str.startsWith('--')) return str; // CSS custom properties (variables) \u2014 leave unchanged\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// SSR error serialization is exported above:\n// serializeSSRErrors() \u2014 serialize collected errors to script tag\n// hydrateSSRErrors() \u2014 read errors from DOM during client hydration\n// getSSRErrors() \u2014 programmatic access to collected errors\n\n// Re-export server actions\nexport {\n action,\n formAction,\n useAction,\n useFormAction,\n useOptimistic,\n useMutation,\n onRevalidate,\n invalidatePath,\n handleActionRequest,\n getRegisteredActions,\n generateCsrfToken,\n validateCsrfToken,\n csrfMetaTag,\n} from './actions.js';\n", "// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(result => ({ status: 200, body: result }))\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["// What Framework - Server\n// SSR, static site generation, server components.\n// Zero-JS pages by default. Islands opt-in to client JS.\n\nimport { h } from 'what-core';\n\n// --- SSR Error Collection ---\n// Errors that occur during SSR are collected and serialized into the HTML output\n// so the client can pick them up during hydration and display/report them.\n\nlet _ssrErrors = [];\nconst MAX_SSR_ERRORS = 50;\n\nfunction _collectSSRError(error, context = {}) {\n const entry = {\n code: error.code || 'ERR_SSR_RENDER',\n message: error.message || String(error),\n component: context.component || null,\n timestamp: Date.now(),\n };\n // In dev mode, include extra detail for debugging\n if (_isDevMode) {\n entry.suggestion = error.suggestion || null;\n entry.stack = error.stack?.split('\\n').slice(0, 5).join('\\n') || null;\n }\n _ssrErrors.push(entry);\n if (_ssrErrors.length > MAX_SSR_ERRORS) _ssrErrors.shift();\n}\n\nfunction _resetSSRErrors() {\n _ssrErrors = [];\n}\n\n/**\n * Serialize collected SSR errors into a script tag for client hydration.\n * In dev mode: includes full error details (message, suggestion, stack).\n * In production: includes only error code and component name.\n */\nexport function serializeSSRErrors() {\n if (_ssrErrors.length === 0) return '';\n const payload = _isDevMode\n ? _ssrErrors\n : _ssrErrors.map(e => ({ code: e.code, component: e.component }));\n const json = JSON.stringify(payload).replace(/<\\//g, '<\\\\/'); // prevent XSS via </script>\n return `<script type=\"application/json\" data-what-ssr-errors>${json}</script>`;\n}\n\n/**\n * Read SSR errors from the DOM during client hydration.\n * Call this on the client side during hydration to pick up errors from SSR.\n * Returns an array of error objects, or empty array if none.\n */\nexport function hydrateSSRErrors() {\n if (typeof document === 'undefined') return [];\n const el = document.querySelector('script[data-what-ssr-errors]');\n if (!el) return [];\n try {\n const errors = JSON.parse(el.textContent);\n el.remove(); // clean up after reading\n return errors;\n } catch {\n return [];\n }\n}\n\n/**\n * Get collected SSR errors (for programmatic access before serialization).\n */\nexport function getSSRErrors() {\n return _ssrErrors.slice();\n}\n\n// --- Hydration ID Generator ---\nlet _hydrationIdCounter = 0;\n\nfunction resetHydrationId() {\n _hydrationIdCounter = 0;\n}\n\nfunction nextHydrationId() {\n return 'h' + (_hydrationIdCounter++);\n}\n\n// --- Render to Hydratable String ---\n// Renders with hydration markers (data-hk attributes, comment boundaries)\n// so the client can reuse the server-rendered DOM.\n\nexport function renderToHydratableString(vnode) {\n resetHydrationId();\n _resetSSRErrors();\n return _renderHydratable(vnode);\n}\n\nfunction _renderHydratable(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap\n if (typeof vnode === 'function' && vnode._signal) {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n }\n\n // Reactive function child \u2014 wrap in dynamic content markers\n if (typeof vnode === 'function') {\n try {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '<!--$--><!--/$-->';\n }\n }\n\n // Array \u2014 wrap in list markers\n if (Array.isArray(vnode)) {\n return `<!--[]-->${vnode.map(_renderHydratable).join('')}<!--/[]-->`;\n }\n\n // Component \u2014 add hydration key to root element\n if (typeof vnode.tag === 'function') {\n const hkId = nextHydrationId();\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n const html = _renderHydratable(result);\n // Inject data-hk into the first element tag if present\n return injectHydrationKey(html, hkId);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!--ssr-error:${escapeHtml(componentName)}-->`;\n }\n return `<!--ssr-error-->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(_renderHydratable).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// Inject data-hk=\"id\" into the first HTML opening tag\nfunction injectHydrationKey(html, hkId) {\n // Skip comment markers to find the first real element\n const match = html.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);\n if (match) {\n const prefix = match[1];\n const tagName = match[2];\n const insertAt = prefix.length + 1 + tagName.length; // after '<tagName'\n return html.slice(0, insertAt) + ` data-hk=\"${hkId}\"` + html.slice(insertAt);\n }\n return html;\n}\n\n// --- Render to String ---\n// Renders a VNode tree to an HTML string. Used for SSR and static gen.\n\nexport function renderToString(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n return renderToString(vnode());\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n return renderToString(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '';\n }\n }\n\n // Array\n if (Array.isArray(vnode)) {\n return vnode.map(renderToString).join('');\n }\n\n // Component\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n return renderToString(result);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message)} -->`;\n }\n return `<!-- SSR Error -->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(renderToString).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// --- Stream Render ---\n// Returns an async iterator for streaming SSR.\n\nexport async function* renderToStream(vnode) {\n if (vnode == null || vnode === false || vnode === true) return;\n\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n yield escapeHtml(String(vnode));\n return;\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n yield* renderToStream(vnode());\n return;\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n yield* renderToStream(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in stream SSR:', e.message);\n }\n }\n return;\n }\n\n if (Array.isArray(vnode)) {\n for (const child of vnode) {\n yield* renderToStream(child);\n }\n return;\n }\n\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n // Support async components\n const resolved = result instanceof Promise ? await result : result;\n yield* renderToStream(resolved);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in stream SSR:`, e.message);\n }\n yield _isDevMode\n ? `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message || 'Component error')} -->`\n : `<!-- SSR Error -->`;\n }\n return;\n }\n\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n yield `<${tag}${attrs}>`;\n\n if (!VOID_ELEMENTS.has(tag)) {\n const rawInner = _resolveInnerHTML(props);\n if (rawInner != null) {\n yield String(rawInner);\n } else {\n for (const child of children) {\n yield* renderToStream(child);\n }\n }\n yield `</${tag}>`;\n }\n}\n\n// --- Static Site Generation ---\n\nexport function definePage(config) {\n return {\n // 'static' = pre-render at build time (default)\n // 'server' = render on each request\n // 'client' = render in browser (SPA)\n // 'hybrid' = static shell + islands\n mode: 'static',\n ...config,\n };\n}\n\n// Generate static HTML for a page\nexport function generateStaticPage(page, data = {}) {\n _resetSSRErrors();\n const vnode = page.component(data);\n const html = renderToString(vnode);\n const islands = page.islands || [];\n\n return wrapDocument({\n title: page.title || '',\n meta: page.meta || {},\n body: html,\n islands,\n scripts: page.mode === 'static' ? [] : page.scripts || [],\n styles: page.styles || [],\n mode: page.mode,\n ssrErrors: serializeSSRErrors(),\n });\n}\n\nfunction wrapDocument({ title, meta, body, islands, scripts, styles, mode, ssrErrors = '' }) {\n const metaTags = Object.entries(meta)\n .map(([name, content]) => `<meta name=\"${escapeHtml(name)}\" content=\"${escapeHtml(content)}\">`)\n .join('\\n ');\n\n const styleTags = styles\n .map(href => `<link rel=\"stylesheet\" href=\"${escapeHtml(href)}\">`)\n .join('\\n ');\n\n const islandScript = islands.length > 0 ? `\n <script type=\"module\">\n import { hydrateIslands } from '/@what/islands.js';\n hydrateIslands();\n </script>` : '';\n\n const scriptTags = scripts\n .map(src => `<script type=\"module\" src=\"${escapeHtml(src)}\"></script>`)\n .join('\\n ');\n\n const clientScript = mode === 'client' ? `\n <script type=\"module\" src=\"/@what/client.js\"></script>` : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n ${metaTags}\n <title>${escapeHtml(title)}</title>\n ${styleTags}\n </head>\n <body>\n <div id=\"app\">${body}</div>\n ${ssrErrors}\n ${islandScript}\n ${scriptTags}\n ${clientScript}\n </body>\n</html>`;\n}\n\n// --- Server Component ---\n// Renders on the server, sends HTML to client. No JS shipped.\n\nexport function server(Component) {\n Component._server = true;\n return Component;\n}\n\n// --- Helpers ---\n\n// Dev-mode flag for server\nconst _isDevMode = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n/**\n * Resolve innerHTML / dangerouslySetInnerHTML from props.\n * Requires { __html: ... } wrapper. Plain string innerHTML is rejected (XSS prevention).\n */\nfunction _resolveInnerHTML(props) {\n if (!props) return null;\n\n // dangerouslySetInnerHTML always requires { __html }\n if (props.dangerouslySetInnerHTML) {\n return props.dangerouslySetInnerHTML.__html ?? null;\n }\n\n // innerHTML with { __html } wrapper \u2014 allowed\n if (props.innerHTML && typeof props.innerHTML === 'object' && '__html' in props.innerHTML) {\n return props.innerHTML.__html ?? null;\n }\n\n // innerHTML as plain string \u2014 reject with warning\n if (props.innerHTML != null && typeof props.innerHTML === 'string') {\n if (_isDevMode) {\n console.warn(\n '[what-server] innerHTML received a raw string. This is a security risk (XSS). ' +\n 'Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead.'\n );\n }\n return null;\n }\n\n return null;\n}\n\nfunction renderAttrs(props) {\n let out = '';\n for (const [key, val] of Object.entries(props)) {\n if (key === 'key' || key === 'ref' || key === 'children' || key === 'dangerouslySetInnerHTML' || key === 'innerHTML') continue;\n if (key.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR\n if (val === false || val == null) continue;\n\n const attrName = getHtmlAttributeName(key);\n if (!isValidHtmlAttributeName(attrName)) {\n if (_isDevMode) console.warn(`[what-server] Omitted invalid attribute name: ${key}`);\n continue;\n }\n if (!isSafeUrlAttributeValue(attrName, val)) {\n if (_isDevMode) console.warn(`[what-server] Omitted unsafe URL attribute \"${attrName}\": ${val}`);\n continue;\n }\n\n if (key === 'className' || key === 'class') {\n out += ` class=\"${escapeHtml(String(val))}\"`;\n } else if (key === 'style' && typeof val === 'object') {\n const css = Object.entries(val)\n .map(([p, v]) => `${camelToKebab(p)}:${v}`)\n .join(';');\n out += ` style=\"${escapeHtml(css)}\"`;\n } else if (val === true) {\n // ARIA attributes require explicit =\"true\", HTML boolean attrs can be bare\n if (attrName.startsWith('aria-') || attrName === 'role') {\n out += ` ${attrName}=\"true\"`;\n } else {\n out += ` ${attrName}`;\n }\n } else {\n out += ` ${attrName}=\"${escapeHtml(String(val))}\"`;\n }\n }\n\n return out;\n}\n\nfunction getHtmlAttributeName(name) {\n if (name === 'className') return 'class';\n if (name === 'htmlFor') return 'for';\n return name;\n}\n\nfunction isValidHtmlAttributeName(name) {\n return /^[^\\s\"'>/=\\x00-\\x1f\\x7f]+$/.test(name);\n}\n\nconst URL_ATTRS = new Set([\n 'href',\n 'src',\n 'action',\n 'formaction',\n 'poster',\n 'cite',\n 'background',\n 'xlink:href',\n]);\nconst URL_LIST_ATTRS = new Set(['srcset']);\n\nfunction isSafeUrlAttributeValue(name, value) {\n const normalizedName = String(name).toLowerCase();\n if (URL_LIST_ATTRS.has(normalizedName)) return isSafeSrcsetValue(value);\n if (URL_ATTRS.has(normalizedName)) return isSafeUrlValue(value);\n return true;\n}\n\nfunction isSafeUrlValue(value) {\n if (typeof value !== 'string') return true;\n const normalized = value.trim().replace(/[\\s\\x00-\\x1f\\x7f]/g, '').toLowerCase();\n return !(normalized.startsWith('javascript:') || normalized.startsWith('data:') || normalized.startsWith('vbscript:'));\n}\n\nfunction isSafeSrcsetValue(value) {\n if (typeof value !== 'string') return true;\n return value\n .split(',')\n .every(candidate => {\n const url = candidate.trim().split(/\\s+/, 1)[0] || '';\n return url === '' || isSafeUrlValue(url);\n });\n}\n\nfunction escapeHtml(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction camelToKebab(str) {\n if (str.startsWith('--')) return str; // CSS custom properties (variables) \u2014 leave unchanged\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// SSR error serialization is exported above:\n// serializeSSRErrors() \u2014 serialize collected errors to script tag\n// hydrateSSRErrors() \u2014 read errors from DOM during client hydration\n// getSSRErrors() \u2014 programmatic access to collected errors\n\n// Re-export server actions\nexport {\n action,\n formAction,\n useAction,\n useFormAction,\n useOptimistic,\n useMutation,\n onRevalidate,\n invalidatePath,\n handleActionRequest,\n getRegisteredActions,\n generateCsrfToken,\n validateCsrfToken,\n csrfMetaTag,\n} from './actions.js';\n", "// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(result => ({ status: 200, body: result }))\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["action"]
|
|
7
7
|
}
|
package/dist/index.min.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import"what-core";import{signal as y,batch as
|
|
1
|
+
import"what-core";import{signal as y,batch as k}from"what-core";var _=new Map;function O(){if(typeof document<"u"){let t=document.querySelector('meta[name="what-csrf-token"]');if(t)return t.getAttribute("content");let e=document.cookie.match(/(?:^|;\s*)what-csrf=([^;]+)/);if(e)return decodeURIComponent(e[1])}return null}function U(){if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let t=new Uint8Array(16);return crypto.getRandomValues(t),Array.from(t,e=>e.toString(16).padStart(2,"0")).join("")}throw new Error("[what] No secure random source available for CSRF token generation")}function j(t,e){if(!t||!e||t.length!==e.length)return!1;let r=0;for(let n=0;n<t.length;n++)r|=t.charCodeAt(n)^e.charCodeAt(n);return r===0}function D(t){return`<meta name="what-csrf-token" content="${String(t).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}">`}var F=0;function V(){return`a_${typeof crypto<"u"&&crypto.getRandomValues?Array.from(crypto.getRandomValues(new Uint8Array(6)),e=>e.toString(16).padStart(2,"0")).join(""):`c${(++F).toString(36)}_${Date.now().toString(36)}`}`}function W(t,e={}){let r=e.id||V(),{onError:n,onSuccess:s,revalidate:o}=e;typeof window>"u"&&_.set(r,{fn:t,options:e});async function i(...a){if(typeof window>"u")return t(...a);let c=e.timeout||3e4,l=new AbortController,u=setTimeout(()=>l.abort(),c);try{let f=O(),p={"Content-Type":"application/json","X-What-Action":r};f&&(p["X-CSRF-Token"]=f);let d=await fetch("/__what_action",{method:"POST",headers:p,credentials:"same-origin",signal:l.signal,body:JSON.stringify({args:a})});if(!d.ok){let b=await d.json().catch(()=>({message:"Action failed"}));throw new Error(b.message||"Action failed")}let h=await d.json();if(s&&s(h),o)for(let b of o)H(b);return h}catch(f){if(f.name==="AbortError"){let p=new Error(`Action "${r}" timed out after ${c}ms`);throw p.code="TIMEOUT",n&&n(p),p}throw n&&n(f),f}finally{clearTimeout(u)}}return i._actionId=r,i._isAction=!0,i}function I(t,e={}){let{onSuccess:r,onError:n,resetOnSuccess:s=!0}=e;return async o=>{let i,a;o instanceof Event?(o.preventDefault(),a=o.target,i=new FormData(a)):i=o;let c={},l=!1;for(let[u,f]of i.entries())typeof File<"u"&&f instanceof File&&(l=!0),c[u]?Array.isArray(c[u])?c[u].push(f):c[u]=[c[u],f]:c[u]=f;try{let u=l?await t(c,i):await t(c);return r&&r(u,a),s&&a&&a.reset(),u}catch(u){throw n&&n(u,a),u}}}function M(t){let e=y(!1),r=y(null),n=y(null);async function s(...o){e.set(!0),r.set(null);try{let i=await t(...o);return n.set(i),i}catch(i){throw r.set(i),i}finally{e.set(!1)}}return{trigger:s,isPending:()=>e(),error:()=>r(),data:()=>n(),reset:()=>{r.set(null),n.set(null)}}}function z(t,e={}){let{resetOnSuccess:r=!0}=e,n={current:null},s=M(I(t,{resetOnSuccess:r}));function o(i){i.preventDefault();let a=new FormData(i.target);return n.current=i.target,s.trigger(a)}return{...s,handleSubmit:o,formRef:n}}function X(t,e){let r=y(t),n=y([]),s=y(t);function o(l){let u=e(r.peek(),l);k(()=>{n.set([...n.peek(),l]),r.set(u)})}function i(l,u){k(()=>{if(n.set(n.peek().filter(f=>f!==l)),u!==void 0){s.set(u);let f=u;for(let p of n.peek())f=e(f,p);r.set(f)}})}function a(l,u){k(()=>{let f=n.peek().filter(h=>h!==l);n.set(f);let p=u!==void 0?u:s.peek();s.set(p);let d=p;for(let h of f)d=e(d,h);r.set(d)})}async function c(l,u){o(l);try{let f=await u();return i(l,f),f}catch(f){throw a(l),f}}return{value:()=>r(),isPending:()=>n().length>0,addOptimistic:o,resolve:i,rollback:a,withOptimistic:c,set:l=>{r.set(l),s.set(l)}}}var R=new Map;function q(t,e){return R.has(t)||R.set(t,new Set),R.get(t).add(e),()=>{R.get(t)?.delete(e)}}function H(t){let e=R.get(t);if(e)for(let r of e)try{r()}catch(n){console.error("[what] Revalidation error:",n)}}function J(t,e,r,n={}){let{csrfToken:s,skipCsrf:o=!1}=n;if(!o){if(!s)return Promise.resolve({status:500,body:{message:"[what] CSRF token not configured. Pass { csrfToken: sessionToken } to handleActionRequest, or { skipCsrf: true } to explicitly opt out."}});let a=t?.headers?.["x-csrf-token"]||t?.headers?.["X-CSRF-Token"];if(!j(a,s))return Promise.resolve({status:403,body:{message:"Invalid CSRF token"}})}let i=_.get(e);return i?Array.isArray(r)?i.fn(...r).then(a=>({status:200,body:a})).catch(a=>(console.error(`[what] Action "${e}" error:`,a),{status:500,body:{message:"Action failed"}})):Promise.resolve({status:400,body:{message:"Invalid action arguments"}}):Promise.resolve({status:404,body:{message:"Action not found"}})}function Z(){return[..._.keys()]}function K(t,e={}){let{onSuccess:r,onError:n,onSettled:s}=e,o={isPending:y(!1),error:y(null),data:y(null)};async function i(...a){o.isPending.set(!0),o.error.set(null);try{let c=await t(...a);return o.data.set(c),r&&r(c,...a),c}catch(c){throw o.error.set(c),n&&n(c,...a),c}finally{o.isPending.set(!1),s&&s(o.data.peek(),o.error.peek(),...a)}}return{mutate:i,isPending:()=>o.isPending(),error:()=>o.error(),data:()=>o.data(),reset:()=>{o.error.set(null),o.data.set(null)}}}var S=[],Y=50;function A(t,e={}){let r={code:t.code||"ERR_SSR_RENDER",message:t.message||String(t),component:e.component||null,timestamp:Date.now()};g&&(r.suggestion=t.suggestion||null,r.stack=t.stack?.split(`
|
|
2
2
|
`).slice(0,5).join(`
|
|
3
|
-
`)||null),S.push(
|
|
3
|
+
`)||null),S.push(r),S.length>Y&&S.shift()}function L(){S=[]}function B(){if(S.length===0)return"";let t=g?S:S.map(r=>({code:r.code,component:r.component}));return`<script type="application/json" data-what-ssr-errors>${JSON.stringify(t).replace(/<\//g,"<\\/")}<\/script>`}function mt(){if(typeof document>"u")return[];let t=document.querySelector("script[data-what-ssr-errors]");if(!t)return[];try{let e=JSON.parse(t.textContent);return t.remove(),e}catch{return[]}}function pt(){return S.slice()}var N=0;function G(){N=0}function Q(){return"h"+N++}function dt(t){return G(),L(),w(t)}function w(t){if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return m(String(t));if(typeof t=="function"&&t._signal)return`<!--$-->${w(t())}<!--/$-->`;if(typeof t=="function")try{return`<!--$-->${w(t())}<!--/$-->`}catch(c){return A(c,{component:"reactive-function"}),g&&console.warn("[what-server] Error rendering reactive function in SSR:",c.message),"<!--$--><!--/$-->"}if(Array.isArray(t))return`<!--[]-->${t.map(w).join("")}<!--/[]-->`;if(typeof t.tag=="function"){let c=Q(),l=t.tag.displayName||t.tag.name||"Anonymous";try{let u=t.tag({...t.props,children:t.children}),f=w(u);return v(f,c)}catch(u){return A(u,{component:l}),g?(console.warn(`[what-server] Error rendering component "${l}" in SSR:`,u.message),`<!--ssr-error:${m(l)}-->`):"<!--ssr-error-->"}}let{tag:e,props:r,children:n}=t,s=E(r||{}),o=`<${e}${s}>`;if(C.has(e))return o;let i=x(r),a=i!=null?String(i):n.map(w).join("");return`${o}${a}</${e}>`}function v(t,e){let r=t.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);if(r){let n=r[1],s=r[2],o=n.length+1+s.length;return t.slice(0,o)+` data-hk="${e}"`+t.slice(o)}return t}function $(t){if(t==null||t===!1||t===!0)return"";if(typeof t=="string"||typeof t=="number")return m(String(t));if(typeof t=="function"&&t._signal)return $(t());if(typeof t=="function")try{return $(t())}catch(c){return A(c,{component:"reactive-function"}),g&&console.warn("[what-server] Error rendering reactive function in SSR:",c.message),""}if(Array.isArray(t))return t.map($).join("");if(typeof t.tag=="function"){let c=t.tag.displayName||t.tag.name||"Anonymous";try{let l=t.tag({...t.props,children:t.children});return $(l)}catch(l){return A(l,{component:c}),g?(console.warn(`[what-server] Error rendering component "${c}" in SSR:`,l.message),`<!-- SSR Error in ${m(c)}: ${m(l.message)} -->`):"<!-- SSR Error -->"}}let{tag:e,props:r,children:n}=t,s=E(r||{}),o=`<${e}${s}>`;if(C.has(e))return o;let i=x(r),a=i!=null?String(i):n.map($).join("");return`${o}${a}</${e}>`}async function*T(t){if(t==null||t===!1||t===!0)return;if(typeof t=="string"||typeof t=="number"){yield m(String(t));return}if(typeof t=="function"&&t._signal){yield*T(t());return}if(typeof t=="function"){try{yield*T(t())}catch(o){A(o,{component:"reactive-function"}),g&&console.warn("[what-server] Error rendering reactive function in stream SSR:",o.message)}return}if(Array.isArray(t)){for(let o of t)yield*T(o);return}if(typeof t.tag=="function"){let o=t.tag.displayName||t.tag.name||"Anonymous";try{let i=t.tag({...t.props,children:t.children}),a=i instanceof Promise?await i:i;yield*T(a)}catch(i){A(i,{component:o}),g&&console.warn(`[what-server] Error rendering component "${o}" in stream SSR:`,i.message),yield g?`<!-- SSR Error in ${m(o)}: ${m(i.message||"Component error")} -->`:"<!-- SSR Error -->"}return}let{tag:e,props:r,children:n}=t,s=E(r||{});if(yield`<${e}${s}>`,!C.has(e)){let o=x(r);if(o!=null)yield String(o);else for(let i of n)yield*T(i);yield`</${e}>`}}function gt(t){return{mode:"static",...t}}function yt(t,e={}){L();let r=t.component(e),n=$(r),s=t.islands||[];return tt({title:t.title||"",meta:t.meta||{},body:n,islands:s,scripts:t.mode==="static"?[]:t.scripts||[],styles:t.styles||[],mode:t.mode,ssrErrors:B()})}function tt({title:t,meta:e,body:r,islands:n,scripts:s,styles:o,mode:i,ssrErrors:a=""}){let c=Object.entries(e).map(([d,h])=>`<meta name="${m(d)}" content="${m(h)}">`).join(`
|
|
4
4
|
`),l=o.map(d=>`<link rel="stylesheet" href="${m(d)}">`).join(`
|
|
5
5
|
`),u=n.length>0?`
|
|
6
6
|
<script type="module">
|
|
7
7
|
import { hydrateIslands } from '/@what/islands.js';
|
|
8
8
|
hydrateIslands();
|
|
9
|
-
<\/script>`:"",f=
|
|
10
|
-
`),p=
|
|
9
|
+
<\/script>`:"",f=s.map(d=>`<script type="module" src="${m(d)}"><\/script>`).join(`
|
|
10
|
+
`),p=i==="client"?`
|
|
11
11
|
<script type="module" src="/@what/client.js"><\/script>`:"";return`<!DOCTYPE html>
|
|
12
12
|
<html lang="en">
|
|
13
13
|
<head>
|
|
14
14
|
<meta charset="UTF-8">
|
|
15
15
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
16
|
-
${
|
|
16
|
+
${c}
|
|
17
17
|
<title>${m(t)}</title>
|
|
18
18
|
${l}
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
|
21
|
-
<div id="app">${
|
|
22
|
-
${
|
|
21
|
+
<div id="app">${r}</div>
|
|
22
|
+
${a}
|
|
23
23
|
${u}
|
|
24
24
|
${f}
|
|
25
25
|
${p}
|
|
26
26
|
</body>
|
|
27
|
-
</html>`}function
|
|
27
|
+
</html>`}function ht(t){return t._server=!0,t}var g=!(typeof process<"u");function x(t){return t?t.dangerouslySetInnerHTML?t.dangerouslySetInnerHTML.__html??null:t.innerHTML&&typeof t.innerHTML=="object"&&"__html"in t.innerHTML?t.innerHTML.__html??null:(t.innerHTML!=null&&typeof t.innerHTML=="string"&&g&&console.warn("[what-server] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."),null):null}function E(t){let e="";for(let[r,n]of Object.entries(t)){if(r==="key"||r==="ref"||r==="children"||r==="dangerouslySetInnerHTML"||r==="innerHTML"||r.startsWith("on")&&r.length>2||n===!1||n==null)continue;let s=et(r);if(!rt(s)){g&&console.warn(`[what-server] Omitted invalid attribute name: ${r}`);continue}if(!it(s,n)){g&&console.warn(`[what-server] Omitted unsafe URL attribute "${s}": ${n}`);continue}if(r==="className"||r==="class")e+=` class="${m(String(n))}"`;else if(r==="style"&&typeof n=="object"){let o=Object.entries(n).map(([i,a])=>`${at(i)}:${a}`).join(";");e+=` style="${m(o)}"`}else n===!0?s.startsWith("aria-")||s==="role"?e+=` ${s}="true"`:e+=` ${s}`:e+=` ${s}="${m(String(n))}"`}return e}function et(t){return t==="className"?"class":t==="htmlFor"?"for":t}function rt(t){return/^[^\s"'>/=\x00-\x1f\x7f]+$/.test(t)}var nt=new Set(["href","src","action","formaction","poster","cite","background","xlink:href"]),ot=new Set(["srcset"]);function it(t,e){let r=String(t).toLowerCase();return ot.has(r)?st(e):nt.has(r)?P(e):!0}function P(t){if(typeof t!="string")return!0;let e=t.trim().replace(/[\s\x00-\x1f\x7f]/g,"").toLowerCase();return!(e.startsWith("javascript:")||e.startsWith("data:")||e.startsWith("vbscript:"))}function st(t){return typeof t!="string"?!0:t.split(",").every(e=>{let r=e.trim().split(/\s+/,1)[0]||"";return r===""||P(r)})}function m(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function at(t){return t.startsWith("--")?t:t.replace(/([A-Z])/g,"-$1").toLowerCase()}var C=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);export{W as action,D as csrfMetaTag,gt as definePage,I as formAction,U as generateCsrfToken,yt as generateStaticPage,Z as getRegisteredActions,pt as getSSRErrors,J as handleActionRequest,mt as hydrateSSRErrors,H as invalidatePath,q as onRevalidate,dt as renderToHydratableString,T as renderToStream,$ as renderToString,B as serializeSSRErrors,ht as server,M as useAction,z as useFormAction,K as useMutation,X as useOptimistic,j as validateCsrfToken};
|
|
28
28
|
//# sourceMappingURL=index.min.js.map
|
package/dist/index.min.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.js", "../src/actions.js"],
|
|
4
|
-
"sourcesContent": ["// What Framework - Server\n// SSR, static site generation, server components.\n// Zero-JS pages by default. Islands opt-in to client JS.\n\nimport { h } from 'what-core';\n\n// --- SSR Error Collection ---\n// Errors that occur during SSR are collected and serialized into the HTML output\n// so the client can pick them up during hydration and display/report them.\n\nlet _ssrErrors = [];\nconst MAX_SSR_ERRORS = 50;\n\nfunction _collectSSRError(error, context = {}) {\n const entry = {\n code: error.code || 'ERR_SSR_RENDER',\n message: error.message || String(error),\n component: context.component || null,\n timestamp: Date.now(),\n };\n // In dev mode, include extra detail for debugging\n if (_isDevMode) {\n entry.suggestion = error.suggestion || null;\n entry.stack = error.stack?.split('\\n').slice(0, 5).join('\\n') || null;\n }\n _ssrErrors.push(entry);\n if (_ssrErrors.length > MAX_SSR_ERRORS) _ssrErrors.shift();\n}\n\nfunction _resetSSRErrors() {\n _ssrErrors = [];\n}\n\n/**\n * Serialize collected SSR errors into a script tag for client hydration.\n * In dev mode: includes full error details (message, suggestion, stack).\n * In production: includes only error code and component name.\n */\nexport function serializeSSRErrors() {\n if (_ssrErrors.length === 0) return '';\n const payload = _isDevMode\n ? _ssrErrors\n : _ssrErrors.map(e => ({ code: e.code, component: e.component }));\n const json = JSON.stringify(payload).replace(/<\\//g, '<\\\\/'); // prevent XSS via </script>\n return `<script type=\"application/json\" data-what-ssr-errors>${json}</script>`;\n}\n\n/**\n * Read SSR errors from the DOM during client hydration.\n * Call this on the client side during hydration to pick up errors from SSR.\n * Returns an array of error objects, or empty array if none.\n */\nexport function hydrateSSRErrors() {\n if (typeof document === 'undefined') return [];\n const el = document.querySelector('script[data-what-ssr-errors]');\n if (!el) return [];\n try {\n const errors = JSON.parse(el.textContent);\n el.remove(); // clean up after reading\n return errors;\n } catch {\n return [];\n }\n}\n\n/**\n * Get collected SSR errors (for programmatic access before serialization).\n */\nexport function getSSRErrors() {\n return _ssrErrors.slice();\n}\n\n// --- Hydration ID Generator ---\nlet _hydrationIdCounter = 0;\n\nfunction resetHydrationId() {\n _hydrationIdCounter = 0;\n}\n\nfunction nextHydrationId() {\n return 'h' + (_hydrationIdCounter++);\n}\n\n// --- Render to Hydratable String ---\n// Renders with hydration markers (data-hk attributes, comment boundaries)\n// so the client can reuse the server-rendered DOM.\n\nexport function renderToHydratableString(vnode) {\n resetHydrationId();\n _resetSSRErrors();\n return _renderHydratable(vnode);\n}\n\nfunction _renderHydratable(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap\n if (typeof vnode === 'function' && vnode._signal) {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n }\n\n // Reactive function child \u2014 wrap in dynamic content markers\n if (typeof vnode === 'function') {\n try {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '<!--$--><!--/$-->';\n }\n }\n\n // Array \u2014 wrap in list markers\n if (Array.isArray(vnode)) {\n return `<!--[]-->${vnode.map(_renderHydratable).join('')}<!--/[]-->`;\n }\n\n // Component \u2014 add hydration key to root element\n if (typeof vnode.tag === 'function') {\n const hkId = nextHydrationId();\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n const html = _renderHydratable(result);\n // Inject data-hk into the first element tag if present\n return injectHydrationKey(html, hkId);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!--ssr-error:${escapeHtml(componentName)}-->`;\n }\n return `<!--ssr-error-->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(_renderHydratable).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// Inject data-hk=\"id\" into the first HTML opening tag\nfunction injectHydrationKey(html, hkId) {\n // Skip comment markers to find the first real element\n const match = html.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);\n if (match) {\n const prefix = match[1];\n const tagName = match[2];\n const insertAt = prefix.length + 1 + tagName.length; // after '<tagName'\n return html.slice(0, insertAt) + ` data-hk=\"${hkId}\"` + html.slice(insertAt);\n }\n return html;\n}\n\n// --- Render to String ---\n// Renders a VNode tree to an HTML string. Used for SSR and static gen.\n\nexport function renderToString(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n return renderToString(vnode());\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n return renderToString(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '';\n }\n }\n\n // Array\n if (Array.isArray(vnode)) {\n return vnode.map(renderToString).join('');\n }\n\n // Component\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n return renderToString(result);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message)} -->`;\n }\n return `<!-- SSR Error -->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(renderToString).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// --- Stream Render ---\n// Returns an async iterator for streaming SSR.\n\nexport async function* renderToStream(vnode) {\n if (vnode == null || vnode === false || vnode === true) return;\n\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n yield escapeHtml(String(vnode));\n return;\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n yield* renderToStream(vnode());\n return;\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n yield* renderToStream(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in stream SSR:', e.message);\n }\n }\n return;\n }\n\n if (Array.isArray(vnode)) {\n for (const child of vnode) {\n yield* renderToStream(child);\n }\n return;\n }\n\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n // Support async components\n const resolved = result instanceof Promise ? await result : result;\n yield* renderToStream(resolved);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in stream SSR:`, e.message);\n }\n yield _isDevMode\n ? `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message || 'Component error')} -->`\n : `<!-- SSR Error -->`;\n }\n return;\n }\n\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n yield `<${tag}${attrs}>`;\n\n if (!VOID_ELEMENTS.has(tag)) {\n const rawInner = _resolveInnerHTML(props);\n if (rawInner != null) {\n yield String(rawInner);\n } else {\n for (const child of children) {\n yield* renderToStream(child);\n }\n }\n yield `</${tag}>`;\n }\n}\n\n// --- Static Site Generation ---\n\nexport function definePage(config) {\n return {\n // 'static' = pre-render at build time (default)\n // 'server' = render on each request\n // 'client' = render in browser (SPA)\n // 'hybrid' = static shell + islands\n mode: 'static',\n ...config,\n };\n}\n\n// Generate static HTML for a page\nexport function generateStaticPage(page, data = {}) {\n _resetSSRErrors();\n const vnode = page.component(data);\n const html = renderToString(vnode);\n const islands = page.islands || [];\n\n return wrapDocument({\n title: page.title || '',\n meta: page.meta || {},\n body: html,\n islands,\n scripts: page.mode === 'static' ? [] : page.scripts || [],\n styles: page.styles || [],\n mode: page.mode,\n ssrErrors: serializeSSRErrors(),\n });\n}\n\nfunction wrapDocument({ title, meta, body, islands, scripts, styles, mode, ssrErrors = '' }) {\n const metaTags = Object.entries(meta)\n .map(([name, content]) => `<meta name=\"${escapeHtml(name)}\" content=\"${escapeHtml(content)}\">`)\n .join('\\n ');\n\n const styleTags = styles\n .map(href => `<link rel=\"stylesheet\" href=\"${escapeHtml(href)}\">`)\n .join('\\n ');\n\n const islandScript = islands.length > 0 ? `\n <script type=\"module\">\n import { hydrateIslands } from '/@what/islands.js';\n hydrateIslands();\n </script>` : '';\n\n const scriptTags = scripts\n .map(src => `<script type=\"module\" src=\"${escapeHtml(src)}\"></script>`)\n .join('\\n ');\n\n const clientScript = mode === 'client' ? `\n <script type=\"module\" src=\"/@what/client.js\"></script>` : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n ${metaTags}\n <title>${escapeHtml(title)}</title>\n ${styleTags}\n </head>\n <body>\n <div id=\"app\">${body}</div>\n ${ssrErrors}\n ${islandScript}\n ${scriptTags}\n ${clientScript}\n </body>\n</html>`;\n}\n\n// --- Server Component ---\n// Renders on the server, sends HTML to client. No JS shipped.\n\nexport function server(Component) {\n Component._server = true;\n return Component;\n}\n\n// --- Helpers ---\n\n// Dev-mode flag for server\nconst _isDevMode = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n/**\n * Resolve innerHTML / dangerouslySetInnerHTML from props.\n * Requires { __html: ... } wrapper. Plain string innerHTML is rejected (XSS prevention).\n */\nfunction _resolveInnerHTML(props) {\n if (!props) return null;\n\n // dangerouslySetInnerHTML always requires { __html }\n if (props.dangerouslySetInnerHTML) {\n return props.dangerouslySetInnerHTML.__html ?? null;\n }\n\n // innerHTML with { __html } wrapper \u2014 allowed\n if (props.innerHTML && typeof props.innerHTML === 'object' && '__html' in props.innerHTML) {\n return props.innerHTML.__html ?? null;\n }\n\n // innerHTML as plain string \u2014 reject with warning\n if (props.innerHTML != null && typeof props.innerHTML === 'string') {\n if (_isDevMode) {\n console.warn(\n '[what-server] innerHTML received a raw string. This is a security risk (XSS). ' +\n 'Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead.'\n );\n }\n return null;\n }\n\n return null;\n}\n\nfunction renderAttrs(props) {\n let out = '';\n for (const [key, val] of Object.entries(props)) {\n if (key === 'key' || key === 'ref' || key === 'children' || key === 'dangerouslySetInnerHTML' || key === 'innerHTML') continue;\n if (key.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR\n if (val === false || val == null) continue;\n\n if (key === 'className' || key === 'class') {\n out += ` class=\"${escapeHtml(String(val))}\"`;\n } else if (key === 'style' && typeof val === 'object') {\n const css = Object.entries(val)\n .map(([p, v]) => `${camelToKebab(p)}:${v}`)\n .join(';');\n out += ` style=\"${escapeHtml(css)}\"`;\n } else if (val === true) {\n // ARIA attributes require explicit =\"true\", HTML boolean attrs can be bare\n if (key.startsWith('aria-') || key === 'role') {\n out += ` ${key}=\"true\"`;\n } else {\n out += ` ${key}`;\n }\n } else {\n out += ` ${key}=\"${escapeHtml(String(val))}\"`;\n }\n }\n\n return out;\n}\n\nfunction escapeHtml(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction camelToKebab(str) {\n if (str.startsWith('--')) return str; // CSS custom properties (variables) \u2014 leave unchanged\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// SSR error serialization is exported above:\n// serializeSSRErrors() \u2014 serialize collected errors to script tag\n// hydrateSSRErrors() \u2014 read errors from DOM during client hydration\n// getSSRErrors() \u2014 programmatic access to collected errors\n\n// Re-export server actions\nexport {\n action,\n formAction,\n useAction,\n useFormAction,\n useOptimistic,\n useMutation,\n onRevalidate,\n invalidatePath,\n handleActionRequest,\n getRegisteredActions,\n generateCsrfToken,\n validateCsrfToken,\n csrfMetaTag,\n} from './actions.js';\n", "// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(result => ({ status: 200, body: result }))\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["signal", "batch", "actionRegistry", "getCsrfToken", "meta", "match", "generateCsrfToken", "arr", "b", "validateCsrfToken", "requestToken", "sessionToken", "result", "i", "csrfMetaTag", "token", "_actionCounter", "generateActionId", "action", "fn", "options", "id", "onError", "onSuccess", "revalidate", "callAction", "args", "timeout", "controller", "timeoutId", "csrfToken", "headers", "response", "error", "path", "invalidatePath", "timeoutError", "formAction", "actionFn", "resetOnSuccess", "formDataOrEvent", "formData", "form", "data", "hasFiles", "key", "value", "useAction", "isPending", "trigger", "e", "useFormAction", "formRef", "actionState", "handleSubmit", "useOptimistic", "initialValue", "reducer", "pending", "baseValue", "addOptimistic", "optimisticValue", "resolve", "serverValue", "a", "current", "rollback", "realValue", "newPending", "base", "withOptimistic", "asyncFn", "v", "revalidationCallbacks", "onRevalidate", "callback", "callbacks", "cb", "handleActionRequest", "req", "actionId", "sessionCsrfToken", "skipCsrf", "requestCsrfToken", "getRegisteredActions", "useMutation", "mutationFn", "onSettled", "state", "mutate", "_ssrErrors", "MAX_SSR_ERRORS", "_collectSSRError", "error", "context", "entry", "_isDevMode", "_resetSSRErrors", "serializeSSRErrors", "payload", "hydrateSSRErrors", "el", "errors", "getSSRErrors", "_hydrationIdCounter", "resetHydrationId", "nextHydrationId", "renderToHydratableString", "vnode", "_renderHydratable", "escapeHtml", "
|
|
4
|
+
"sourcesContent": ["// What Framework - Server\n// SSR, static site generation, server components.\n// Zero-JS pages by default. Islands opt-in to client JS.\n\nimport { h } from 'what-core';\n\n// --- SSR Error Collection ---\n// Errors that occur during SSR are collected and serialized into the HTML output\n// so the client can pick them up during hydration and display/report them.\n\nlet _ssrErrors = [];\nconst MAX_SSR_ERRORS = 50;\n\nfunction _collectSSRError(error, context = {}) {\n const entry = {\n code: error.code || 'ERR_SSR_RENDER',\n message: error.message || String(error),\n component: context.component || null,\n timestamp: Date.now(),\n };\n // In dev mode, include extra detail for debugging\n if (_isDevMode) {\n entry.suggestion = error.suggestion || null;\n entry.stack = error.stack?.split('\\n').slice(0, 5).join('\\n') || null;\n }\n _ssrErrors.push(entry);\n if (_ssrErrors.length > MAX_SSR_ERRORS) _ssrErrors.shift();\n}\n\nfunction _resetSSRErrors() {\n _ssrErrors = [];\n}\n\n/**\n * Serialize collected SSR errors into a script tag for client hydration.\n * In dev mode: includes full error details (message, suggestion, stack).\n * In production: includes only error code and component name.\n */\nexport function serializeSSRErrors() {\n if (_ssrErrors.length === 0) return '';\n const payload = _isDevMode\n ? _ssrErrors\n : _ssrErrors.map(e => ({ code: e.code, component: e.component }));\n const json = JSON.stringify(payload).replace(/<\\//g, '<\\\\/'); // prevent XSS via </script>\n return `<script type=\"application/json\" data-what-ssr-errors>${json}</script>`;\n}\n\n/**\n * Read SSR errors from the DOM during client hydration.\n * Call this on the client side during hydration to pick up errors from SSR.\n * Returns an array of error objects, or empty array if none.\n */\nexport function hydrateSSRErrors() {\n if (typeof document === 'undefined') return [];\n const el = document.querySelector('script[data-what-ssr-errors]');\n if (!el) return [];\n try {\n const errors = JSON.parse(el.textContent);\n el.remove(); // clean up after reading\n return errors;\n } catch {\n return [];\n }\n}\n\n/**\n * Get collected SSR errors (for programmatic access before serialization).\n */\nexport function getSSRErrors() {\n return _ssrErrors.slice();\n}\n\n// --- Hydration ID Generator ---\nlet _hydrationIdCounter = 0;\n\nfunction resetHydrationId() {\n _hydrationIdCounter = 0;\n}\n\nfunction nextHydrationId() {\n return 'h' + (_hydrationIdCounter++);\n}\n\n// --- Render to Hydratable String ---\n// Renders with hydration markers (data-hk attributes, comment boundaries)\n// so the client can reuse the server-rendered DOM.\n\nexport function renderToHydratableString(vnode) {\n resetHydrationId();\n _resetSSRErrors();\n return _renderHydratable(vnode);\n}\n\nfunction _renderHydratable(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap\n if (typeof vnode === 'function' && vnode._signal) {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n }\n\n // Reactive function child \u2014 wrap in dynamic content markers\n if (typeof vnode === 'function') {\n try {\n return `<!--$-->${_renderHydratable(vnode())}<!--/$-->`;\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '<!--$--><!--/$-->';\n }\n }\n\n // Array \u2014 wrap in list markers\n if (Array.isArray(vnode)) {\n return `<!--[]-->${vnode.map(_renderHydratable).join('')}<!--/[]-->`;\n }\n\n // Component \u2014 add hydration key to root element\n if (typeof vnode.tag === 'function') {\n const hkId = nextHydrationId();\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n const html = _renderHydratable(result);\n // Inject data-hk into the first element tag if present\n return injectHydrationKey(html, hkId);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!--ssr-error:${escapeHtml(componentName)}-->`;\n }\n return `<!--ssr-error-->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(_renderHydratable).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// Inject data-hk=\"id\" into the first HTML opening tag\nfunction injectHydrationKey(html, hkId) {\n // Skip comment markers to find the first real element\n const match = html.match(/^((?:<!--.*?-->)*)<([a-zA-Z][a-zA-Z0-9-]*)/);\n if (match) {\n const prefix = match[1];\n const tagName = match[2];\n const insertAt = prefix.length + 1 + tagName.length; // after '<tagName'\n return html.slice(0, insertAt) + ` data-hk=\"${hkId}\"` + html.slice(insertAt);\n }\n return html;\n}\n\n// --- Render to String ---\n// Renders a VNode tree to an HTML string. Used for SSR and static gen.\n\nexport function renderToString(vnode) {\n if (vnode == null || vnode === false || vnode === true) return '';\n\n // Text\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return escapeHtml(String(vnode));\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n return renderToString(vnode());\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n return renderToString(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in SSR:', e.message);\n }\n return '';\n }\n }\n\n // Array\n if (Array.isArray(vnode)) {\n return vnode.map(renderToString).join('');\n }\n\n // Component\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n return renderToString(result);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in SSR:`, e.message);\n return `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message)} -->`;\n }\n return `<!-- SSR Error -->`;\n }\n }\n\n // Element\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n const open = `<${tag}${attrs}>`;\n\n // Void elements\n if (VOID_ELEMENTS.has(tag)) return open;\n\n const rawInner = _resolveInnerHTML(props);\n const inner = rawInner != null ? String(rawInner) : children.map(renderToString).join('');\n return `${open}${inner}</${tag}>`;\n}\n\n// --- Stream Render ---\n// Returns an async iterator for streaming SSR.\n\nexport async function* renderToStream(vnode) {\n if (vnode == null || vnode === false || vnode === true) return;\n\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n yield escapeHtml(String(vnode));\n return;\n }\n\n // Signal \u2014 unwrap by calling it\n if (typeof vnode === 'function' && vnode._signal) {\n yield* renderToStream(vnode());\n return;\n }\n\n // Reactive function child \u2014 call to get value\n if (typeof vnode === 'function') {\n try {\n yield* renderToStream(vnode());\n } catch (e) {\n _collectSSRError(e, { component: 'reactive-function' });\n if (_isDevMode) {\n console.warn('[what-server] Error rendering reactive function in stream SSR:', e.message);\n }\n }\n return;\n }\n\n if (Array.isArray(vnode)) {\n for (const child of vnode) {\n yield* renderToStream(child);\n }\n return;\n }\n\n if (typeof vnode.tag === 'function') {\n const componentName = vnode.tag.displayName || vnode.tag.name || 'Anonymous';\n try {\n const result = vnode.tag({ ...vnode.props, children: vnode.children });\n // Support async components\n const resolved = result instanceof Promise ? await result : result;\n yield* renderToStream(resolved);\n } catch (e) {\n _collectSSRError(e, { component: componentName });\n if (_isDevMode) {\n console.warn(`[what-server] Error rendering component \"${componentName}\" in stream SSR:`, e.message);\n }\n yield _isDevMode\n ? `<!-- SSR Error in ${escapeHtml(componentName)}: ${escapeHtml(e.message || 'Component error')} -->`\n : `<!-- SSR Error -->`;\n }\n return;\n }\n\n const { tag, props, children } = vnode;\n const attrs = renderAttrs(props || {});\n yield `<${tag}${attrs}>`;\n\n if (!VOID_ELEMENTS.has(tag)) {\n const rawInner = _resolveInnerHTML(props);\n if (rawInner != null) {\n yield String(rawInner);\n } else {\n for (const child of children) {\n yield* renderToStream(child);\n }\n }\n yield `</${tag}>`;\n }\n}\n\n// --- Static Site Generation ---\n\nexport function definePage(config) {\n return {\n // 'static' = pre-render at build time (default)\n // 'server' = render on each request\n // 'client' = render in browser (SPA)\n // 'hybrid' = static shell + islands\n mode: 'static',\n ...config,\n };\n}\n\n// Generate static HTML for a page\nexport function generateStaticPage(page, data = {}) {\n _resetSSRErrors();\n const vnode = page.component(data);\n const html = renderToString(vnode);\n const islands = page.islands || [];\n\n return wrapDocument({\n title: page.title || '',\n meta: page.meta || {},\n body: html,\n islands,\n scripts: page.mode === 'static' ? [] : page.scripts || [],\n styles: page.styles || [],\n mode: page.mode,\n ssrErrors: serializeSSRErrors(),\n });\n}\n\nfunction wrapDocument({ title, meta, body, islands, scripts, styles, mode, ssrErrors = '' }) {\n const metaTags = Object.entries(meta)\n .map(([name, content]) => `<meta name=\"${escapeHtml(name)}\" content=\"${escapeHtml(content)}\">`)\n .join('\\n ');\n\n const styleTags = styles\n .map(href => `<link rel=\"stylesheet\" href=\"${escapeHtml(href)}\">`)\n .join('\\n ');\n\n const islandScript = islands.length > 0 ? `\n <script type=\"module\">\n import { hydrateIslands } from '/@what/islands.js';\n hydrateIslands();\n </script>` : '';\n\n const scriptTags = scripts\n .map(src => `<script type=\"module\" src=\"${escapeHtml(src)}\"></script>`)\n .join('\\n ');\n\n const clientScript = mode === 'client' ? `\n <script type=\"module\" src=\"/@what/client.js\"></script>` : '';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n ${metaTags}\n <title>${escapeHtml(title)}</title>\n ${styleTags}\n </head>\n <body>\n <div id=\"app\">${body}</div>\n ${ssrErrors}\n ${islandScript}\n ${scriptTags}\n ${clientScript}\n </body>\n</html>`;\n}\n\n// --- Server Component ---\n// Renders on the server, sends HTML to client. No JS shipped.\n\nexport function server(Component) {\n Component._server = true;\n return Component;\n}\n\n// --- Helpers ---\n\n// Dev-mode flag for server\nconst _isDevMode = typeof process !== 'undefined'\n ? process.env?.NODE_ENV !== 'production'\n : true;\n\n/**\n * Resolve innerHTML / dangerouslySetInnerHTML from props.\n * Requires { __html: ... } wrapper. Plain string innerHTML is rejected (XSS prevention).\n */\nfunction _resolveInnerHTML(props) {\n if (!props) return null;\n\n // dangerouslySetInnerHTML always requires { __html }\n if (props.dangerouslySetInnerHTML) {\n return props.dangerouslySetInnerHTML.__html ?? null;\n }\n\n // innerHTML with { __html } wrapper \u2014 allowed\n if (props.innerHTML && typeof props.innerHTML === 'object' && '__html' in props.innerHTML) {\n return props.innerHTML.__html ?? null;\n }\n\n // innerHTML as plain string \u2014 reject with warning\n if (props.innerHTML != null && typeof props.innerHTML === 'string') {\n if (_isDevMode) {\n console.warn(\n '[what-server] innerHTML received a raw string. This is a security risk (XSS). ' +\n 'Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead.'\n );\n }\n return null;\n }\n\n return null;\n}\n\nfunction renderAttrs(props) {\n let out = '';\n for (const [key, val] of Object.entries(props)) {\n if (key === 'key' || key === 'ref' || key === 'children' || key === 'dangerouslySetInnerHTML' || key === 'innerHTML') continue;\n if (key.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR\n if (val === false || val == null) continue;\n\n const attrName = getHtmlAttributeName(key);\n if (!isValidHtmlAttributeName(attrName)) {\n if (_isDevMode) console.warn(`[what-server] Omitted invalid attribute name: ${key}`);\n continue;\n }\n if (!isSafeUrlAttributeValue(attrName, val)) {\n if (_isDevMode) console.warn(`[what-server] Omitted unsafe URL attribute \"${attrName}\": ${val}`);\n continue;\n }\n\n if (key === 'className' || key === 'class') {\n out += ` class=\"${escapeHtml(String(val))}\"`;\n } else if (key === 'style' && typeof val === 'object') {\n const css = Object.entries(val)\n .map(([p, v]) => `${camelToKebab(p)}:${v}`)\n .join(';');\n out += ` style=\"${escapeHtml(css)}\"`;\n } else if (val === true) {\n // ARIA attributes require explicit =\"true\", HTML boolean attrs can be bare\n if (attrName.startsWith('aria-') || attrName === 'role') {\n out += ` ${attrName}=\"true\"`;\n } else {\n out += ` ${attrName}`;\n }\n } else {\n out += ` ${attrName}=\"${escapeHtml(String(val))}\"`;\n }\n }\n\n return out;\n}\n\nfunction getHtmlAttributeName(name) {\n if (name === 'className') return 'class';\n if (name === 'htmlFor') return 'for';\n return name;\n}\n\nfunction isValidHtmlAttributeName(name) {\n return /^[^\\s\"'>/=\\x00-\\x1f\\x7f]+$/.test(name);\n}\n\nconst URL_ATTRS = new Set([\n 'href',\n 'src',\n 'action',\n 'formaction',\n 'poster',\n 'cite',\n 'background',\n 'xlink:href',\n]);\nconst URL_LIST_ATTRS = new Set(['srcset']);\n\nfunction isSafeUrlAttributeValue(name, value) {\n const normalizedName = String(name).toLowerCase();\n if (URL_LIST_ATTRS.has(normalizedName)) return isSafeSrcsetValue(value);\n if (URL_ATTRS.has(normalizedName)) return isSafeUrlValue(value);\n return true;\n}\n\nfunction isSafeUrlValue(value) {\n if (typeof value !== 'string') return true;\n const normalized = value.trim().replace(/[\\s\\x00-\\x1f\\x7f]/g, '').toLowerCase();\n return !(normalized.startsWith('javascript:') || normalized.startsWith('data:') || normalized.startsWith('vbscript:'));\n}\n\nfunction isSafeSrcsetValue(value) {\n if (typeof value !== 'string') return true;\n return value\n .split(',')\n .every(candidate => {\n const url = candidate.trim().split(/\\s+/, 1)[0] || '';\n return url === '' || isSafeUrlValue(url);\n });\n}\n\nfunction escapeHtml(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction camelToKebab(str) {\n if (str.startsWith('--')) return str; // CSS custom properties (variables) \u2014 leave unchanged\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n// SSR error serialization is exported above:\n// serializeSSRErrors() \u2014 serialize collected errors to script tag\n// hydrateSSRErrors() \u2014 read errors from DOM during client hydration\n// getSSRErrors() \u2014 programmatic access to collected errors\n\n// Re-export server actions\nexport {\n action,\n formAction,\n useAction,\n useFormAction,\n useOptimistic,\n useMutation,\n onRevalidate,\n invalidatePath,\n handleActionRequest,\n getRegisteredActions,\n generateCsrfToken,\n validateCsrfToken,\n csrfMetaTag,\n} from './actions.js';\n", "// What Framework - Server Actions\n// Call server-side functions from client code seamlessly.\n// Similar to Next.js Server Actions / SolidStart server functions.\n//\n// Usage:\n// // Define on server\n// const saveUser = action(async (formData) => {\n// 'use server';\n// const user = await db.users.create(formData);\n// return { success: true, id: user.id };\n// });\n//\n// // Call from client\n// const result = await saveUser({ name: 'John' });\n\nimport { signal, batch } from 'what-core';\n\n// Registry of server actions\nconst actionRegistry = new Map();\n\n// --- CSRF Protection ---\n// Server generates a token per session; client sends it with every action request.\n// The token is injected into the page via a meta tag or embedded in the server response.\n\n// Client: read the CSRF token from the page meta tag or cookie\n// Re-reads on every call to handle token rotation\nfunction getCsrfToken() {\n if (typeof document !== 'undefined') {\n // Try meta tag first\n const meta = document.querySelector('meta[name=\"what-csrf-token\"]');\n if (meta) {\n return meta.getAttribute('content');\n }\n // Try cookie\n const match = document.cookie.match(/(?:^|;\\s*)what-csrf=([^;]+)/);\n if (match) {\n return decodeURIComponent(match[1]);\n }\n }\n return null;\n}\n\n// Server: generate a CSRF token (call this per session/request)\nexport function generateCsrfToken() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID \u2014 use crypto.getRandomValues\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(16);\n crypto.getRandomValues(arr);\n return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');\n }\n // Last resort \u2014 should not be reached in modern environments\n throw new Error('[what] No secure random source available for CSRF token generation');\n}\n\n// Server: validate CSRF token from request header against session token\nexport function validateCsrfToken(requestToken, sessionToken) {\n if (!requestToken || !sessionToken) return false;\n // Constant-time comparison to prevent timing attacks\n if (requestToken.length !== sessionToken.length) return false;\n let result = 0;\n for (let i = 0; i < requestToken.length; i++) {\n result |= requestToken.charCodeAt(i) ^ sessionToken.charCodeAt(i);\n }\n return result === 0;\n}\n\n// Server: middleware helper to inject CSRF meta tag into HTML\nexport function csrfMetaTag(token) {\n // HTML-escape the token to prevent XSS if a non-standard value is used\n const escaped = String(token).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n return `<meta name=\"what-csrf-token\" content=\"${escaped}\">`;\n}\n\n// --- Define a server action ---\n\nlet _actionCounter = 0;\n\nfunction generateActionId() {\n // Generate a deterministic ID \u2014 prefer crypto.getRandomValues, fall back to a\n // monotonic counter (never Math.random, which is not cryptographically safe and\n // produces predictable IDs in some runtimes).\n const rand = typeof crypto !== 'undefined' && crypto.getRandomValues\n ? Array.from(crypto.getRandomValues(new Uint8Array(6)), b => b.toString(16).padStart(2, '0')).join('')\n : `c${(++_actionCounter).toString(36)}_${Date.now().toString(36)}`;\n return `a_${rand}`;\n}\n\nexport function action(fn, options = {}) {\n const id = options.id || generateActionId();\n const { onError, onSuccess, revalidate } = options;\n\n // Server-side: register the action\n if (typeof window === 'undefined') {\n actionRegistry.set(id, { fn, options });\n }\n\n // Create the callable wrapper\n async function callAction(...args) {\n // Server-side: call directly\n if (typeof window === 'undefined') {\n return fn(...args);\n }\n\n // Client-side: call via fetch with timeout support\n const timeout = options.timeout || 30000; // Default 30s timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const csrfToken = getCsrfToken();\n const headers = {\n 'Content-Type': 'application/json',\n 'X-What-Action': id,\n };\n if (csrfToken) headers['X-CSRF-Token'] = csrfToken;\n\n const response = await fetch('/__what_action', {\n method: 'POST',\n headers,\n credentials: 'same-origin',\n signal: controller.signal,\n body: JSON.stringify({ args }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Action failed' }));\n throw new Error(error.message || 'Action failed');\n }\n\n const result = await response.json();\n\n if (onSuccess) onSuccess(result);\n if (revalidate) {\n // Trigger revalidation of specified paths\n for (const path of revalidate) {\n invalidatePath(path);\n }\n }\n\n return result;\n } catch (error) {\n if (error.name === 'AbortError') {\n const timeoutError = new Error(`Action \"${id}\" timed out after ${timeout}ms`);\n timeoutError.code = 'TIMEOUT';\n if (onError) onError(timeoutError);\n throw timeoutError;\n }\n if (onError) onError(error);\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n callAction._actionId = id;\n callAction._isAction = true;\n\n return callAction;\n}\n\n// --- Form action helper ---\n// For forms that submit to server actions.\n\nexport function formAction(actionFn, options = {}) {\n const { onSuccess, onError, resetOnSuccess = true } = options;\n\n return async (formDataOrEvent) => {\n let formData;\n let form;\n\n if (formDataOrEvent instanceof Event) {\n formDataOrEvent.preventDefault();\n form = formDataOrEvent.target;\n formData = new FormData(form);\n } else {\n formData = formDataOrEvent;\n }\n\n // Convert FormData to plain object, preserving File instances\n const data = {};\n let hasFiles = false;\n for (const [key, value] of formData.entries()) {\n if (typeof File !== 'undefined' && value instanceof File) {\n hasFiles = true;\n }\n if (data[key]) {\n // Handle multiple values (e.g., checkboxes, multi-file inputs)\n if (Array.isArray(data[key])) {\n data[key].push(value);\n } else {\n data[key] = [data[key], value];\n }\n } else {\n data[key] = value;\n }\n }\n\n try {\n // If form contains files, pass the raw FormData as second arg\n // so the action handler can access files directly\n const result = hasFiles\n ? await actionFn(data, formData)\n : await actionFn(data);\n if (onSuccess) onSuccess(result, form);\n if (resetOnSuccess && form) form.reset();\n return result;\n } catch (error) {\n if (onError) onError(error, form);\n throw error;\n }\n };\n}\n\n// --- useAction hook ---\n// Returns action state and trigger function.\n\nexport function useAction(actionFn) {\n const isPending = signal(false);\n const error = signal(null);\n const data = signal(null);\n\n async function trigger(...args) {\n isPending.set(true);\n error.set(null);\n\n try {\n const result = await actionFn(...args);\n data.set(result);\n return result;\n } catch (e) {\n error.set(e);\n throw e;\n } finally {\n isPending.set(false);\n }\n }\n\n return {\n trigger,\n isPending: () => isPending(),\n error: () => error(),\n data: () => data(),\n reset: () => {\n error.set(null);\n data.set(null);\n },\n };\n}\n\n// --- useFormAction hook ---\n// Combines useAction with form handling.\n\nexport function useFormAction(actionFn, options = {}) {\n const { resetOnSuccess = true } = options;\n const formRef = { current: null };\n const actionState = useAction(formAction(actionFn, { resetOnSuccess }));\n\n function handleSubmit(e) {\n e.preventDefault();\n const formData = new FormData(e.target);\n formRef.current = e.target;\n return actionState.trigger(formData);\n }\n\n return {\n ...actionState,\n handleSubmit,\n formRef,\n };\n}\n\n// --- Optimistic updates ---\n\nexport function useOptimistic(initialValue, reducer) {\n const value = signal(initialValue);\n const pending = signal([]);\n const baseValue = signal(initialValue); // Track the confirmed server value\n\n function addOptimistic(action) {\n const optimisticValue = reducer(value.peek(), action);\n batch(() => {\n pending.set([...pending.peek(), action]);\n value.set(optimisticValue);\n });\n }\n\n function resolve(action, serverValue) {\n batch(() => {\n pending.set(pending.peek().filter(a => a !== action));\n if (serverValue !== undefined) {\n baseValue.set(serverValue);\n // Recompute optimistic state from new base + remaining pending actions\n let current = serverValue;\n for (const a of pending.peek()) {\n current = reducer(current, a);\n }\n value.set(current);\n }\n });\n }\n\n function rollback(action, realValue) {\n batch(() => {\n const newPending = pending.peek().filter(a => a !== action);\n pending.set(newPending);\n const base = realValue !== undefined ? realValue : baseValue.peek();\n baseValue.set(base);\n // Recompute from base + remaining pending actions\n let current = base;\n for (const a of newPending) {\n current = reducer(current, a);\n }\n value.set(current);\n });\n }\n\n // Auto-rollback helper: wraps an async action with automatic rollback on error\n async function withOptimistic(action, asyncFn) {\n addOptimistic(action);\n try {\n const result = await asyncFn();\n resolve(action, result);\n return result;\n } catch (e) {\n rollback(action);\n throw e;\n }\n }\n\n return {\n value: () => value(),\n isPending: () => pending().length > 0,\n addOptimistic,\n resolve,\n rollback,\n withOptimistic,\n set: (v) => { value.set(v); baseValue.set(v); },\n };\n}\n\n// --- Path revalidation ---\n\nconst revalidationCallbacks = new Map();\n\nexport function onRevalidate(path, callback) {\n if (!revalidationCallbacks.has(path)) {\n revalidationCallbacks.set(path, new Set());\n }\n revalidationCallbacks.get(path).add(callback);\n\n return () => {\n revalidationCallbacks.get(path)?.delete(callback);\n };\n}\n\nexport function invalidatePath(path) {\n const callbacks = revalidationCallbacks.get(path);\n if (callbacks) {\n for (const cb of callbacks) {\n try { cb(); } catch (e) { console.error('[what] Revalidation error:', e); }\n }\n }\n}\n\n// --- Server-side action handler ---\n// Add this to your server middleware.\n\nexport function handleActionRequest(req, actionId, args, options = {}) {\n const { csrfToken: sessionCsrfToken, skipCsrf = false } = options;\n\n // Validate CSRF token unless explicitly skipped\n if (!skipCsrf) {\n if (!sessionCsrfToken) {\n // Fail closed: no CSRF token configured means the developer forgot to set it up.\n // This prevents silent security vulnerabilities in production.\n return Promise.resolve({\n status: 500,\n body: {\n message: '[what] CSRF token not configured. ' +\n 'Pass { csrfToken: sessionToken } to handleActionRequest, ' +\n 'or { skipCsrf: true } to explicitly opt out.'\n }\n });\n }\n const requestCsrfToken = req?.headers?.['x-csrf-token'] || req?.headers?.['X-CSRF-Token'];\n if (!validateCsrfToken(requestCsrfToken, sessionCsrfToken)) {\n return Promise.resolve({ status: 403, body: { message: 'Invalid CSRF token' } });\n }\n }\n\n const action = actionRegistry.get(actionId);\n if (!action) {\n return Promise.resolve({ status: 404, body: { message: 'Action not found' } });\n }\n\n // Validate args is an array to prevent prototype pollution\n if (!Array.isArray(args)) {\n return Promise.resolve({ status: 400, body: { message: 'Invalid action arguments' } });\n }\n\n return action.fn(...args)\n .then(result => ({ status: 200, body: result }))\n .catch(error => {\n // Log the full error server-side, return generic message to client\n console.error(`[what] Action \"${actionId}\" error:`, error);\n return {\n status: 500,\n body: { message: 'Action failed' },\n };\n });\n}\n\n// --- Get all registered actions (for SSR/build) ---\n\nexport function getRegisteredActions() {\n return [...actionRegistry.keys()];\n}\n\n// --- Mutation helper ---\n// Like useSWR mutation but simpler.\n\nexport function useMutation(mutationFn, options = {}) {\n const { onSuccess, onError, onSettled } = options;\n\n const state = {\n isPending: signal(false),\n error: signal(null),\n data: signal(null),\n };\n\n async function mutate(...args) {\n state.isPending.set(true);\n state.error.set(null);\n\n try {\n const result = await mutationFn(...args);\n state.data.set(result);\n if (onSuccess) onSuccess(result, ...args);\n return result;\n } catch (error) {\n state.error.set(error);\n if (onError) onError(error, ...args);\n throw error;\n } finally {\n state.isPending.set(false);\n if (onSettled) onSettled(state.data.peek(), state.error.peek(), ...args);\n }\n }\n\n return {\n mutate,\n isPending: () => state.isPending(),\n error: () => state.error(),\n data: () => state.data(),\n reset: () => {\n state.error.set(null);\n state.data.set(null);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["signal", "batch", "actionRegistry", "getCsrfToken", "meta", "match", "generateCsrfToken", "arr", "b", "validateCsrfToken", "requestToken", "sessionToken", "result", "i", "csrfMetaTag", "token", "_actionCounter", "generateActionId", "action", "fn", "options", "id", "onError", "onSuccess", "revalidate", "callAction", "args", "timeout", "controller", "timeoutId", "csrfToken", "headers", "response", "error", "path", "invalidatePath", "timeoutError", "formAction", "actionFn", "resetOnSuccess", "formDataOrEvent", "formData", "form", "data", "hasFiles", "key", "value", "useAction", "isPending", "trigger", "e", "useFormAction", "formRef", "actionState", "handleSubmit", "useOptimistic", "initialValue", "reducer", "pending", "baseValue", "addOptimistic", "optimisticValue", "resolve", "serverValue", "a", "current", "rollback", "realValue", "newPending", "base", "withOptimistic", "asyncFn", "v", "revalidationCallbacks", "onRevalidate", "callback", "callbacks", "cb", "handleActionRequest", "req", "actionId", "sessionCsrfToken", "skipCsrf", "requestCsrfToken", "getRegisteredActions", "useMutation", "mutationFn", "onSettled", "state", "mutate", "_ssrErrors", "MAX_SSR_ERRORS", "_collectSSRError", "error", "context", "entry", "_isDevMode", "_resetSSRErrors", "serializeSSRErrors", "payload", "e", "hydrateSSRErrors", "el", "errors", "getSSRErrors", "_hydrationIdCounter", "resetHydrationId", "nextHydrationId", "renderToHydratableString", "vnode", "_renderHydratable", "escapeHtml", "hkId", "componentName", "result", "html", "injectHydrationKey", "tag", "props", "children", "attrs", "renderAttrs", "open", "VOID_ELEMENTS", "rawInner", "_resolveInnerHTML", "inner", "match", "prefix", "tagName", "insertAt", "renderToString", "renderToStream", "child", "resolved", "definePage", "config", "generateStaticPage", "page", "data", "islands", "wrapDocument", "title", "meta", "body", "scripts", "styles", "mode", "ssrErrors", "metaTags", "name", "content", "styleTags", "href", "islandScript", "scriptTags", "src", "clientScript", "server", "Component", "out", "key", "val", "attrName", "getHtmlAttributeName", "isValidHtmlAttributeName", "isSafeUrlAttributeValue", "css", "p", "v", "camelToKebab", "URL_ATTRS", "URL_LIST_ATTRS", "value", "normalizedName", "isSafeSrcsetValue", "isSafeUrlValue", "normalized", "candidate", "url", "str"]
|
|
7
7
|
}
|
package/dist/islands.js
CHANGED
|
@@ -56,7 +56,10 @@ function serializeIslandStores() {
|
|
|
56
56
|
for (const [name, store] of sharedStores) {
|
|
57
57
|
data[name] = store._getSnapshot();
|
|
58
58
|
}
|
|
59
|
-
return JSON.stringify(data);
|
|
59
|
+
return escapeJsonForScript(JSON.stringify(data));
|
|
60
|
+
}
|
|
61
|
+
function escapeJsonForScript(json) {
|
|
62
|
+
return json.replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
60
63
|
}
|
|
61
64
|
function hydrateIslandStores(serialized) {
|
|
62
65
|
try {
|
package/dist/islands.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/islands.js"],
|
|
4
|
-
"sourcesContent": ["// What Framework - Islands Architecture\n// Each interactive piece of the page is an \"island\" \u2014 a self-contained\n// component that hydrates independently. The rest is static HTML.\n//\n// Features:\n// - Multiple hydration modes (load, idle, visible, action, media, static)\n// - Shared state across islands\n// - Priority-based hydration queue\n// - Progressive enhancement\n//\n// Modes:\n// 'static' - No JS shipped. Pure HTML. (nav, footer, etc.)\n// 'idle' - Hydrate when browser is idle (requestIdleCallback)\n// 'visible' - Hydrate when scrolled into view (IntersectionObserver)\n// 'load' - Hydrate immediately on page load\n// 'media' - Hydrate when media query matches (e.g., mobile-only)\n// 'action' - Hydrate on first user interaction (click, focus, hover)\n\nimport { mount, hydrate, signal, batch } from 'what-core';\n\nconst islandRegistry = new Map();\nconst hydratedIslands = new Set();\nconst hydrationQueue = [];\nlet isProcessingQueue = false;\n\n// --- Shared Island State ---\n// Global reactive store that persists across islands and page navigations\n\nconst sharedStores = new Map();\n\nexport function createIslandStore(name, initialState) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n\n const store = {};\n const signals = {};\n\n // Create signals for each key in initial state\n for (const [key, value] of Object.entries(initialState)) {\n signals[key] = signal(value);\n Object.defineProperty(store, key, {\n get: () => signals[key](),\n set: (val) => signals[key].set(val),\n enumerable: true,\n });\n }\n\n // Methods to interact with store\n store._signals = signals;\n store._subscribe = (key, fn) => {\n if (signals[key]) {\n return signals[key].subscribe(fn);\n }\n };\n store._batch = (fn) => batch(fn);\n store._getSnapshot = () => {\n const snapshot = {};\n for (const [key, sig] of Object.entries(signals)) {\n snapshot[key] = sig.peek();\n }\n return snapshot;\n };\n store._hydrate = (data) => {\n batch(() => {\n for (const [key, value] of Object.entries(data)) {\n if (signals[key]) {\n signals[key].set(value);\n }\n }\n });\n };\n\n sharedStores.set(name, store);\n return store;\n}\n\n// Get or create a shared store\nexport function useIslandStore(name, fallbackInitial = {}) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n return createIslandStore(name, fallbackInitial);\n}\n\n// Serialize all shared stores for SSR\nexport function serializeIslandStores() {\n const data = {};\n for (const [name, store] of sharedStores) {\n data[name] = store._getSnapshot();\n }\n return JSON.stringify(data);\n}\n\n// Hydrate shared stores from SSR data\nexport function hydrateIslandStores(serialized) {\n try {\n const data = typeof serialized === 'string' ? JSON.parse(serialized) : serialized;\n for (const [name, storeData] of Object.entries(data)) {\n const store = useIslandStore(name, storeData);\n store._hydrate(storeData);\n }\n } catch (e) {\n console.warn('[what] Failed to hydrate island stores:', e);\n }\n}\n\n// --- Register an island component ---\n\nexport function island(name, loader, opts = {}) {\n islandRegistry.set(name, {\n loader, // () => import('./MyComponent.js')\n mode: opts.mode || 'idle',\n media: opts.media || null,\n priority: opts.priority || 0, // Higher = hydrate first\n stores: opts.stores || [], // Shared stores this island uses\n });\n}\n\n// --- Island wrapper for SSR ---\n// Renders the static HTML with a marker the client can find.\n\nexport function Island({ name, props = {}, children, mode, priority, stores }) {\n const entry = islandRegistry.get(name);\n const resolvedMode = mode || entry?.mode || 'idle';\n const resolvedPriority = priority ?? entry?.priority ?? 0;\n const resolvedStores = stores || entry?.stores || [];\n\n // Server: render as a div with data attributes for hydration\n return {\n tag: 'div',\n props: {\n 'data-island': name,\n 'data-island-mode': resolvedMode,\n 'data-island-props': JSON.stringify(props),\n 'data-island-priority': resolvedPriority,\n 'data-island-stores': JSON.stringify(resolvedStores),\n },\n children: children || [],\n key: null,\n _vnode: true,\n };\n}\n\n// --- Priority Hydration Queue ---\n\nfunction enqueueHydration(task) {\n // Insert in priority order (higher priority first)\n let inserted = false;\n for (let i = 0; i < hydrationQueue.length; i++) {\n if (task.priority > hydrationQueue[i].priority) {\n hydrationQueue.splice(i, 0, task);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n hydrationQueue.push(task);\n }\n\n processQueue();\n}\n\nfunction processQueue() {\n if (isProcessingQueue || hydrationQueue.length === 0) return;\n isProcessingQueue = true;\n\n // Process one task at a time to avoid blocking\n const task = hydrationQueue.shift();\n\n Promise.resolve(task.hydrate())\n .catch(e => console.error('[what] Island hydration failed:', task.name, e))\n .finally(() => {\n isProcessingQueue = false;\n // Continue processing after a microtask\n queueMicrotask(processQueue);\n });\n}\n\n// Boost priority for an island (e.g., on user interaction)\nexport function boostIslandPriority(name, newPriority = 100) {\n for (const task of hydrationQueue) {\n if (task.name === name) {\n task.priority = newPriority;\n // Re-sort queue\n hydrationQueue.sort((a, b) => b.priority - a.priority);\n break;\n }\n }\n}\n\n// --- Client-side hydration ---\n\nexport function hydrateIslands() {\n // First, hydrate any shared stores from the page\n const storeScript = document.querySelector('script[data-island-stores]');\n if (storeScript) {\n hydrateIslandStores(storeScript.textContent);\n }\n\n const islands = document.querySelectorAll('[data-island]');\n\n for (const el of islands) {\n const name = el.dataset.island;\n const mode = el.dataset.islandMode || 'idle';\n const props = JSON.parse(el.dataset.islandProps || '{}');\n const priority = parseInt(el.dataset.islandPriority || '0', 10);\n const stores = JSON.parse(el.dataset.islandStores || '[]');\n const entry = islandRegistry.get(name);\n\n if (!entry) {\n console.warn(`[what] Island \"${name}\" not registered`);\n continue;\n }\n\n // Skip if already hydrated\n if (hydratedIslands.has(el)) continue;\n\n scheduleHydration(el, entry, props, mode, priority, name, stores);\n }\n}\n\nfunction scheduleHydration(el, entry, props, mode, priority, name, stores) {\n const hydrate = async () => {\n if (hydratedIslands.has(el)) return;\n hydratedIslands.add(el);\n\n const mod = await entry.loader();\n const Component = mod.default || mod;\n\n // Inject shared stores into props\n const storeProps = {};\n for (const storeName of stores) {\n storeProps[storeName] = useIslandStore(storeName);\n }\n\n // Use hydrate() to reuse server-rendered DOM instead of destroying/recreating\n const vnode = Component({ ...props, ...storeProps });\n if (el.childNodes.length > 0) {\n hydrate(vnode, el);\n } else {\n mount(vnode, el);\n }\n\n // Clean up data attributes\n el.removeAttribute('data-island');\n el.removeAttribute('data-island-mode');\n el.removeAttribute('data-island-props');\n el.removeAttribute('data-island-priority');\n el.removeAttribute('data-island-stores');\n\n // Dispatch event for analytics/debugging\n el.dispatchEvent(new CustomEvent('island:hydrated', {\n bubbles: true,\n detail: { name, mode },\n }));\n };\n\n switch (mode) {\n case 'load':\n // Immediate hydration via queue (respects priority)\n enqueueHydration({ name, priority: priority + 1000, hydrate });\n break;\n\n case 'idle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => {\n enqueueHydration({ name, priority, hydrate });\n });\n } else {\n setTimeout(() => {\n enqueueHydration({ name, priority, hydrate });\n }, 200);\n }\n break;\n\n case 'visible': {\n const observer = new IntersectionObserver((entries, obs) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n obs.disconnect();\n enqueueHydration({ name, priority, hydrate });\n break;\n }\n }\n }, { rootMargin: '200px' });\n observer.observe(el);\n break;\n }\n\n case 'media': {\n const mq = window.matchMedia(entry.media || '(max-width: 768px)');\n if (mq.matches) {\n enqueueHydration({ name, priority, hydrate });\n } else {\n mq.addEventListener('change', (e) => {\n if (e.matches) {\n enqueueHydration({ name, priority, hydrate });\n }\n }, { once: true });\n }\n break;\n }\n\n case 'action': {\n const events = ['click', 'focus', 'mouseover', 'touchstart'];\n const handler = () => {\n events.forEach(e => el.removeEventListener(e, handler));\n // Boost priority since user interacted\n enqueueHydration({ name, priority: priority + 500, hydrate });\n };\n events.forEach(e => el.addEventListener(e, handler, { once: true, passive: true }));\n break;\n }\n\n case 'static':\n // Never hydrate\n break;\n\n default:\n enqueueHydration({ name, priority, hydrate });\n }\n}\n\n// --- Auto-discover islands from data attributes ---\n// Call this once on the client to set up all islands.\n\nexport function autoIslands(registry) {\n for (const [name, config] of Object.entries(registry)) {\n island(name, config.loader || config, {\n mode: config.mode || 'idle',\n media: config.media,\n priority: config.priority || 0,\n stores: config.stores || [],\n });\n }\n\n if (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', hydrateIslands);\n } else {\n hydrateIslands();\n }\n }\n}\n\n// --- Progressive Enhancement Helpers ---\n\n// Mark an element as progressively enhanced\nexport function enhance(selector, handler) {\n if (typeof document === 'undefined') return;\n\n const elements = document.querySelectorAll(selector);\n for (const el of elements) {\n if (el.dataset.enhanced) continue;\n el.dataset.enhanced = 'true';\n handler(el);\n }\n}\n\n// Form enhancement: submit via fetch instead of page reload\nexport function enhanceForms(selector = 'form[data-enhance]') {\n enhance(selector, (form) => {\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n\n const formData = new FormData(form);\n const method = form.method.toUpperCase() || 'POST';\n const action = form.action || location.href;\n\n try {\n // Read CSRF token from meta tag\n const csrfMeta = document.querySelector('meta[name=\"csrf-token\"]') ||\n document.querySelector('meta[name=\"what-csrf-token\"]');\n const csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : null;\n\n // If no CSRF token and form hasn't opted out, block submission\n const noCsrf = form.getAttribute('data-no-csrf') === 'true';\n if (!csrfToken && !noCsrf) {\n console.warn(\n '[what] Form submission blocked: no CSRF token found. ' +\n 'Add a <meta name=\"csrf-token\"> tag or set data-no-csrf=\"true\" on the form to opt out.'\n );\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error: new Error('Missing CSRF token') },\n }));\n return;\n }\n\n const headers = {\n 'X-Requested-With': 'XMLHttpRequest',\n };\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n const response = await fetch(action, {\n method,\n body: method === 'GET' ? undefined : formData,\n headers,\n });\n\n form.dispatchEvent(new CustomEvent('form:response', {\n bubbles: true,\n detail: { response, ok: response.ok },\n }));\n } catch (error) {\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error },\n }));\n }\n });\n });\n}\n\n// --- Debugging ---\n\nexport function getIslandStatus() {\n const status = {\n registered: [...islandRegistry.keys()],\n hydrated: hydratedIslands.size,\n pending: hydrationQueue.length,\n queue: hydrationQueue.map(t => ({ name: t.name, priority: t.priority })),\n stores: [...sharedStores.keys()],\n };\n return status;\n}\n"],
|
|
5
|
-
"mappings": ";AAkBA,SAAS,OAAO,SAAS,QAAQ,aAAa;AAE9C,IAAM,iBAAiB,oBAAI,IAAI;AAC/B,IAAM,kBAAkB,oBAAI,IAAI;AAChC,IAAM,iBAAiB,CAAC;AACxB,IAAI,oBAAoB;AAKxB,IAAM,eAAe,oBAAI,IAAI;AAEtB,SAAS,kBAAkB,MAAM,cAAc;AACpD,MAAI,aAAa,IAAI,IAAI,GAAG;AAC1B,WAAO,aAAa,IAAI,IAAI;AAAA,EAC9B;AAEA,QAAM,QAAQ,CAAC;AACf,QAAM,UAAU,CAAC;AAGjB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,YAAQ,GAAG,IAAI,OAAO,KAAK;AAC3B,WAAO,eAAe,OAAO,KAAK;AAAA,MAChC,KAAK,MAAM,QAAQ,GAAG,EAAE;AAAA,MACxB,KAAK,CAAC,QAAQ,QAAQ,GAAG,EAAE,IAAI,GAAG;AAAA,MAClC,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAGA,QAAM,WAAW;AACjB,QAAM,aAAa,CAAC,KAAK,OAAO;AAC9B,QAAI,QAAQ,GAAG,GAAG;AAChB,aAAO,QAAQ,GAAG,EAAE,UAAU,EAAE;AAAA,IAClC;AAAA,EACF;AACA,QAAM,SAAS,CAAC,OAAO,MAAM,EAAE;AAC/B,QAAM,eAAe,MAAM;AACzB,UAAM,WAAW,CAAC;AAClB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,OAAO,GAAG;AAChD,eAAS,GAAG,IAAI,IAAI,KAAK;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AACA,QAAM,WAAW,CAAC,SAAS;AACzB,UAAM,MAAM;AACV,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,YAAI,QAAQ,GAAG,GAAG;AAChB,kBAAQ,GAAG,EAAE,IAAI,KAAK;AAAA,QACxB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,eAAa,IAAI,MAAM,KAAK;AAC5B,SAAO;AACT;AAGO,SAAS,eAAe,MAAM,kBAAkB,CAAC,GAAG;AACzD,MAAI,aAAa,IAAI,IAAI,GAAG;AAC1B,WAAO,aAAa,IAAI,IAAI;AAAA,EAC9B;AACA,SAAO,kBAAkB,MAAM,eAAe;AAChD;AAGO,SAAS,wBAAwB;AACtC,QAAM,OAAO,CAAC;AACd,aAAW,CAAC,MAAM,KAAK,KAAK,cAAc;AACxC,SAAK,IAAI,IAAI,MAAM,aAAa;AAAA,EAClC;AACA,SAAO,KAAK,UAAU,IAAI;
|
|
4
|
+
"sourcesContent": ["// What Framework - Islands Architecture\n// Each interactive piece of the page is an \"island\" \u2014 a self-contained\n// component that hydrates independently. The rest is static HTML.\n//\n// Features:\n// - Multiple hydration modes (load, idle, visible, action, media, static)\n// - Shared state across islands\n// - Priority-based hydration queue\n// - Progressive enhancement\n//\n// Modes:\n// 'static' - No JS shipped. Pure HTML. (nav, footer, etc.)\n// 'idle' - Hydrate when browser is idle (requestIdleCallback)\n// 'visible' - Hydrate when scrolled into view (IntersectionObserver)\n// 'load' - Hydrate immediately on page load\n// 'media' - Hydrate when media query matches (e.g., mobile-only)\n// 'action' - Hydrate on first user interaction (click, focus, hover)\n\nimport { mount, hydrate, signal, batch } from 'what-core';\n\nconst islandRegistry = new Map();\nconst hydratedIslands = new Set();\nconst hydrationQueue = [];\nlet isProcessingQueue = false;\n\n// --- Shared Island State ---\n// Global reactive store that persists across islands and page navigations\n\nconst sharedStores = new Map();\n\nexport function createIslandStore(name, initialState) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n\n const store = {};\n const signals = {};\n\n // Create signals for each key in initial state\n for (const [key, value] of Object.entries(initialState)) {\n signals[key] = signal(value);\n Object.defineProperty(store, key, {\n get: () => signals[key](),\n set: (val) => signals[key].set(val),\n enumerable: true,\n });\n }\n\n // Methods to interact with store\n store._signals = signals;\n store._subscribe = (key, fn) => {\n if (signals[key]) {\n return signals[key].subscribe(fn);\n }\n };\n store._batch = (fn) => batch(fn);\n store._getSnapshot = () => {\n const snapshot = {};\n for (const [key, sig] of Object.entries(signals)) {\n snapshot[key] = sig.peek();\n }\n return snapshot;\n };\n store._hydrate = (data) => {\n batch(() => {\n for (const [key, value] of Object.entries(data)) {\n if (signals[key]) {\n signals[key].set(value);\n }\n }\n });\n };\n\n sharedStores.set(name, store);\n return store;\n}\n\n// Get or create a shared store\nexport function useIslandStore(name, fallbackInitial = {}) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n return createIslandStore(name, fallbackInitial);\n}\n\n// Serialize all shared stores for SSR\nexport function serializeIslandStores() {\n const data = {};\n for (const [name, store] of sharedStores) {\n data[name] = store._getSnapshot();\n }\n return escapeJsonForScript(JSON.stringify(data));\n}\n\nfunction escapeJsonForScript(json) {\n return json\n .replace(/</g, '\\\\u003C')\n .replace(/>/g, '\\\\u003E')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n// Hydrate shared stores from SSR data\nexport function hydrateIslandStores(serialized) {\n try {\n const data = typeof serialized === 'string' ? JSON.parse(serialized) : serialized;\n for (const [name, storeData] of Object.entries(data)) {\n const store = useIslandStore(name, storeData);\n store._hydrate(storeData);\n }\n } catch (e) {\n console.warn('[what] Failed to hydrate island stores:', e);\n }\n}\n\n// --- Register an island component ---\n\nexport function island(name, loader, opts = {}) {\n islandRegistry.set(name, {\n loader, // () => import('./MyComponent.js')\n mode: opts.mode || 'idle',\n media: opts.media || null,\n priority: opts.priority || 0, // Higher = hydrate first\n stores: opts.stores || [], // Shared stores this island uses\n });\n}\n\n// --- Island wrapper for SSR ---\n// Renders the static HTML with a marker the client can find.\n\nexport function Island({ name, props = {}, children, mode, priority, stores }) {\n const entry = islandRegistry.get(name);\n const resolvedMode = mode || entry?.mode || 'idle';\n const resolvedPriority = priority ?? entry?.priority ?? 0;\n const resolvedStores = stores || entry?.stores || [];\n\n // Server: render as a div with data attributes for hydration\n return {\n tag: 'div',\n props: {\n 'data-island': name,\n 'data-island-mode': resolvedMode,\n 'data-island-props': JSON.stringify(props),\n 'data-island-priority': resolvedPriority,\n 'data-island-stores': JSON.stringify(resolvedStores),\n },\n children: children || [],\n key: null,\n _vnode: true,\n };\n}\n\n// --- Priority Hydration Queue ---\n\nfunction enqueueHydration(task) {\n // Insert in priority order (higher priority first)\n let inserted = false;\n for (let i = 0; i < hydrationQueue.length; i++) {\n if (task.priority > hydrationQueue[i].priority) {\n hydrationQueue.splice(i, 0, task);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n hydrationQueue.push(task);\n }\n\n processQueue();\n}\n\nfunction processQueue() {\n if (isProcessingQueue || hydrationQueue.length === 0) return;\n isProcessingQueue = true;\n\n // Process one task at a time to avoid blocking\n const task = hydrationQueue.shift();\n\n Promise.resolve(task.hydrate())\n .catch(e => console.error('[what] Island hydration failed:', task.name, e))\n .finally(() => {\n isProcessingQueue = false;\n // Continue processing after a microtask\n queueMicrotask(processQueue);\n });\n}\n\n// Boost priority for an island (e.g., on user interaction)\nexport function boostIslandPriority(name, newPriority = 100) {\n for (const task of hydrationQueue) {\n if (task.name === name) {\n task.priority = newPriority;\n // Re-sort queue\n hydrationQueue.sort((a, b) => b.priority - a.priority);\n break;\n }\n }\n}\n\n// --- Client-side hydration ---\n\nexport function hydrateIslands() {\n // First, hydrate any shared stores from the page\n const storeScript = document.querySelector('script[data-island-stores]');\n if (storeScript) {\n hydrateIslandStores(storeScript.textContent);\n }\n\n const islands = document.querySelectorAll('[data-island]');\n\n for (const el of islands) {\n const name = el.dataset.island;\n const mode = el.dataset.islandMode || 'idle';\n const props = JSON.parse(el.dataset.islandProps || '{}');\n const priority = parseInt(el.dataset.islandPriority || '0', 10);\n const stores = JSON.parse(el.dataset.islandStores || '[]');\n const entry = islandRegistry.get(name);\n\n if (!entry) {\n console.warn(`[what] Island \"${name}\" not registered`);\n continue;\n }\n\n // Skip if already hydrated\n if (hydratedIslands.has(el)) continue;\n\n scheduleHydration(el, entry, props, mode, priority, name, stores);\n }\n}\n\nfunction scheduleHydration(el, entry, props, mode, priority, name, stores) {\n const hydrate = async () => {\n if (hydratedIslands.has(el)) return;\n hydratedIslands.add(el);\n\n const mod = await entry.loader();\n const Component = mod.default || mod;\n\n // Inject shared stores into props\n const storeProps = {};\n for (const storeName of stores) {\n storeProps[storeName] = useIslandStore(storeName);\n }\n\n // Use hydrate() to reuse server-rendered DOM instead of destroying/recreating\n const vnode = Component({ ...props, ...storeProps });\n if (el.childNodes.length > 0) {\n hydrate(vnode, el);\n } else {\n mount(vnode, el);\n }\n\n // Clean up data attributes\n el.removeAttribute('data-island');\n el.removeAttribute('data-island-mode');\n el.removeAttribute('data-island-props');\n el.removeAttribute('data-island-priority');\n el.removeAttribute('data-island-stores');\n\n // Dispatch event for analytics/debugging\n el.dispatchEvent(new CustomEvent('island:hydrated', {\n bubbles: true,\n detail: { name, mode },\n }));\n };\n\n switch (mode) {\n case 'load':\n // Immediate hydration via queue (respects priority)\n enqueueHydration({ name, priority: priority + 1000, hydrate });\n break;\n\n case 'idle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => {\n enqueueHydration({ name, priority, hydrate });\n });\n } else {\n setTimeout(() => {\n enqueueHydration({ name, priority, hydrate });\n }, 200);\n }\n break;\n\n case 'visible': {\n const observer = new IntersectionObserver((entries, obs) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n obs.disconnect();\n enqueueHydration({ name, priority, hydrate });\n break;\n }\n }\n }, { rootMargin: '200px' });\n observer.observe(el);\n break;\n }\n\n case 'media': {\n const mq = window.matchMedia(entry.media || '(max-width: 768px)');\n if (mq.matches) {\n enqueueHydration({ name, priority, hydrate });\n } else {\n mq.addEventListener('change', (e) => {\n if (e.matches) {\n enqueueHydration({ name, priority, hydrate });\n }\n }, { once: true });\n }\n break;\n }\n\n case 'action': {\n const events = ['click', 'focus', 'mouseover', 'touchstart'];\n const handler = () => {\n events.forEach(e => el.removeEventListener(e, handler));\n // Boost priority since user interacted\n enqueueHydration({ name, priority: priority + 500, hydrate });\n };\n events.forEach(e => el.addEventListener(e, handler, { once: true, passive: true }));\n break;\n }\n\n case 'static':\n // Never hydrate\n break;\n\n default:\n enqueueHydration({ name, priority, hydrate });\n }\n}\n\n// --- Auto-discover islands from data attributes ---\n// Call this once on the client to set up all islands.\n\nexport function autoIslands(registry) {\n for (const [name, config] of Object.entries(registry)) {\n island(name, config.loader || config, {\n mode: config.mode || 'idle',\n media: config.media,\n priority: config.priority || 0,\n stores: config.stores || [],\n });\n }\n\n if (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', hydrateIslands);\n } else {\n hydrateIslands();\n }\n }\n}\n\n// --- Progressive Enhancement Helpers ---\n\n// Mark an element as progressively enhanced\nexport function enhance(selector, handler) {\n if (typeof document === 'undefined') return;\n\n const elements = document.querySelectorAll(selector);\n for (const el of elements) {\n if (el.dataset.enhanced) continue;\n el.dataset.enhanced = 'true';\n handler(el);\n }\n}\n\n// Form enhancement: submit via fetch instead of page reload\nexport function enhanceForms(selector = 'form[data-enhance]') {\n enhance(selector, (form) => {\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n\n const formData = new FormData(form);\n const method = form.method.toUpperCase() || 'POST';\n const action = form.action || location.href;\n\n try {\n // Read CSRF token from meta tag\n const csrfMeta = document.querySelector('meta[name=\"csrf-token\"]') ||\n document.querySelector('meta[name=\"what-csrf-token\"]');\n const csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : null;\n\n // If no CSRF token and form hasn't opted out, block submission\n const noCsrf = form.getAttribute('data-no-csrf') === 'true';\n if (!csrfToken && !noCsrf) {\n console.warn(\n '[what] Form submission blocked: no CSRF token found. ' +\n 'Add a <meta name=\"csrf-token\"> tag or set data-no-csrf=\"true\" on the form to opt out.'\n );\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error: new Error('Missing CSRF token') },\n }));\n return;\n }\n\n const headers = {\n 'X-Requested-With': 'XMLHttpRequest',\n };\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n const response = await fetch(action, {\n method,\n body: method === 'GET' ? undefined : formData,\n headers,\n });\n\n form.dispatchEvent(new CustomEvent('form:response', {\n bubbles: true,\n detail: { response, ok: response.ok },\n }));\n } catch (error) {\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error },\n }));\n }\n });\n });\n}\n\n// --- Debugging ---\n\nexport function getIslandStatus() {\n const status = {\n registered: [...islandRegistry.keys()],\n hydrated: hydratedIslands.size,\n pending: hydrationQueue.length,\n queue: hydrationQueue.map(t => ({ name: t.name, priority: t.priority })),\n stores: [...sharedStores.keys()],\n };\n return status;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["hydrate", "entry"]
|
|
7
7
|
}
|
package/dist/islands.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{mount as E,signal as I,batch as
|
|
1
|
+
import{mount as E,signal as I,batch as v}from"what-core";var y=new Map,m=new Set,l=[],b=!1,p=new Map;function x(e,o){if(p.has(e))return p.get(e);let t={},s={};for(let[n,r]of Object.entries(o))s[n]=I(r),Object.defineProperty(t,n,{get:()=>s[n](),set:a=>s[n].set(a),enumerable:!0});return t._signals=s,t._subscribe=(n,r)=>{if(s[n])return s[n].subscribe(r)},t._batch=n=>v(n),t._getSnapshot=()=>{let n={};for(let[r,a]of Object.entries(s))n[r]=a.peek();return n},t._hydrate=n=>{v(()=>{for(let[r,a]of Object.entries(n))s[r]&&s[r].set(a)})},p.set(e,t),t}function S(e,o={}){return p.has(e)?p.get(e):x(e,o)}function N(){let e={};for(let[o,t]of p)e[o]=t._getSnapshot();return C(JSON.stringify(e))}function C(e){return e.replace(/</g,"\\u003C").replace(/>/g,"\\u003E").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}function O(e){try{let o=typeof e=="string"?JSON.parse(e):e;for(let[t,s]of Object.entries(o))S(t,s)._hydrate(s)}catch(o){console.warn("[what] Failed to hydrate island stores:",o)}}function q(e,o,t={}){y.set(e,{loader:o,mode:t.mode||"idle",media:t.media||null,priority:t.priority||0,stores:t.stores||[]})}function _({name:e,props:o={},children:t,mode:s,priority:n,stores:r}){let a=y.get(e),i=s||a?.mode||"idle",c=n??a?.priority??0,d=r||a?.stores||[];return{tag:"div",props:{"data-island":e,"data-island-mode":i,"data-island-props":JSON.stringify(o),"data-island-priority":c,"data-island-stores":JSON.stringify(d)},children:t||[],key:null,_vnode:!0}}function f(e){let o=!1;for(let t=0;t<l.length;t++)if(e.priority>l[t].priority){l.splice(t,0,e),o=!0;break}o||l.push(e),w()}function w(){if(b||l.length===0)return;b=!0;let e=l.shift();Promise.resolve(e.hydrate()).catch(o=>console.error("[what] Island hydration failed:",e.name,o)).finally(()=>{b=!1,queueMicrotask(w)})}function J(e,o=100){for(let t of l)if(t.name===e){t.priority=o,l.sort((s,n)=>n.priority-s.priority);break}}function k(){let e=document.querySelector("script[data-island-stores]");e&&O(e.textContent);let o=document.querySelectorAll("[data-island]");for(let t of o){let s=t.dataset.island,n=t.dataset.islandMode||"idle",r=JSON.parse(t.dataset.islandProps||"{}"),a=parseInt(t.dataset.islandPriority||"0",10),i=JSON.parse(t.dataset.islandStores||"[]"),c=y.get(s);if(!c){console.warn(`[what] Island "${s}" not registered`);continue}m.has(t)||M(t,c,r,n,a,s,i)}}function M(e,o,t,s,n,r,a){let i=async()=>{if(m.has(e))return;m.add(e);let c=await o.loader(),d=c.default||c,u={};for(let g of a)u[g]=S(g);let h=d({...t,...u});e.childNodes.length>0?i(h,e):E(h,e),e.removeAttribute("data-island"),e.removeAttribute("data-island-mode"),e.removeAttribute("data-island-props"),e.removeAttribute("data-island-priority"),e.removeAttribute("data-island-stores"),e.dispatchEvent(new CustomEvent("island:hydrated",{bubbles:!0,detail:{name:r,mode:s}}))};switch(s){case"load":f({name:r,priority:n+1e3,hydrate:i});break;case"idle":"requestIdleCallback"in window?requestIdleCallback(()=>{f({name:r,priority:n,hydrate:i})}):setTimeout(()=>{f({name:r,priority:n,hydrate:i})},200);break;case"visible":{new IntersectionObserver((d,u)=>{for(let h of d)if(h.isIntersecting){u.disconnect(),f({name:r,priority:n,hydrate:i});break}},{rootMargin:"200px"}).observe(e);break}case"media":{let c=window.matchMedia(o.media||"(max-width: 768px)");c.matches?f({name:r,priority:n,hydrate:i}):c.addEventListener("change",d=>{d.matches&&f({name:r,priority:n,hydrate:i})},{once:!0});break}case"action":{let c=["click","focus","mouseover","touchstart"],d=()=>{c.forEach(u=>e.removeEventListener(u,d)),f({name:r,priority:n+500,hydrate:i})};c.forEach(u=>e.addEventListener(u,d,{once:!0,passive:!0}));break}case"static":break;default:f({name:r,priority:n,hydrate:i})}}function L(e){for(let[o,t]of Object.entries(e))q(o,t.loader||t,{mode:t.mode||"idle",media:t.media,priority:t.priority||0,stores:t.stores||[]});typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",k):k())}function A(e,o){if(typeof document>"u")return;let t=document.querySelectorAll(e);for(let s of t)s.dataset.enhanced||(s.dataset.enhanced="true",o(s))}function j(e="form[data-enhance]"){A(e,o=>{o.addEventListener("submit",async t=>{t.preventDefault();let s=new FormData(o),n=o.method.toUpperCase()||"POST",r=o.action||location.href;try{let a=document.querySelector('meta[name="csrf-token"]')||document.querySelector('meta[name="what-csrf-token"]'),i=a?a.getAttribute("content"):null,c=o.getAttribute("data-no-csrf")==="true";if(!i&&!c){console.warn('[what] Form submission blocked: no CSRF token found. Add a <meta name="csrf-token"> tag or set data-no-csrf="true" on the form to opt out.'),o.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:new Error("Missing CSRF token")}}));return}let d={"X-Requested-With":"XMLHttpRequest"};i&&(d["X-CSRF-Token"]=i);let u=await fetch(r,{method:n,body:n==="GET"?void 0:s,headers:d});o.dispatchEvent(new CustomEvent("form:response",{bubbles:!0,detail:{response:u,ok:u.ok}}))}catch(a){o.dispatchEvent(new CustomEvent("form:error",{bubbles:!0,detail:{error:a}}))}})})}function R(){return{registered:[...y.keys()],hydrated:m.size,pending:l.length,queue:l.map(o=>({name:o.name,priority:o.priority})),stores:[...p.keys()]}}export{_ as Island,L as autoIslands,J as boostIslandPriority,x as createIslandStore,A as enhance,j as enhanceForms,R as getIslandStatus,O as hydrateIslandStores,k as hydrateIslands,q as island,N as serializeIslandStores,S as useIslandStore};
|
|
2
2
|
//# sourceMappingURL=islands.min.js.map
|
package/dist/islands.min.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/islands.js"],
|
|
4
|
-
"sourcesContent": ["// What Framework - Islands Architecture\n// Each interactive piece of the page is an \"island\" \u2014 a self-contained\n// component that hydrates independently. The rest is static HTML.\n//\n// Features:\n// - Multiple hydration modes (load, idle, visible, action, media, static)\n// - Shared state across islands\n// - Priority-based hydration queue\n// - Progressive enhancement\n//\n// Modes:\n// 'static' - No JS shipped. Pure HTML. (nav, footer, etc.)\n// 'idle' - Hydrate when browser is idle (requestIdleCallback)\n// 'visible' - Hydrate when scrolled into view (IntersectionObserver)\n// 'load' - Hydrate immediately on page load\n// 'media' - Hydrate when media query matches (e.g., mobile-only)\n// 'action' - Hydrate on first user interaction (click, focus, hover)\n\nimport { mount, hydrate, signal, batch } from 'what-core';\n\nconst islandRegistry = new Map();\nconst hydratedIslands = new Set();\nconst hydrationQueue = [];\nlet isProcessingQueue = false;\n\n// --- Shared Island State ---\n// Global reactive store that persists across islands and page navigations\n\nconst sharedStores = new Map();\n\nexport function createIslandStore(name, initialState) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n\n const store = {};\n const signals = {};\n\n // Create signals for each key in initial state\n for (const [key, value] of Object.entries(initialState)) {\n signals[key] = signal(value);\n Object.defineProperty(store, key, {\n get: () => signals[key](),\n set: (val) => signals[key].set(val),\n enumerable: true,\n });\n }\n\n // Methods to interact with store\n store._signals = signals;\n store._subscribe = (key, fn) => {\n if (signals[key]) {\n return signals[key].subscribe(fn);\n }\n };\n store._batch = (fn) => batch(fn);\n store._getSnapshot = () => {\n const snapshot = {};\n for (const [key, sig] of Object.entries(signals)) {\n snapshot[key] = sig.peek();\n }\n return snapshot;\n };\n store._hydrate = (data) => {\n batch(() => {\n for (const [key, value] of Object.entries(data)) {\n if (signals[key]) {\n signals[key].set(value);\n }\n }\n });\n };\n\n sharedStores.set(name, store);\n return store;\n}\n\n// Get or create a shared store\nexport function useIslandStore(name, fallbackInitial = {}) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n return createIslandStore(name, fallbackInitial);\n}\n\n// Serialize all shared stores for SSR\nexport function serializeIslandStores() {\n const data = {};\n for (const [name, store] of sharedStores) {\n data[name] = store._getSnapshot();\n }\n return JSON.stringify(data);\n}\n\n// Hydrate shared stores from SSR data\nexport function hydrateIslandStores(serialized) {\n try {\n const data = typeof serialized === 'string' ? JSON.parse(serialized) : serialized;\n for (const [name, storeData] of Object.entries(data)) {\n const store = useIslandStore(name, storeData);\n store._hydrate(storeData);\n }\n } catch (e) {\n console.warn('[what] Failed to hydrate island stores:', e);\n }\n}\n\n// --- Register an island component ---\n\nexport function island(name, loader, opts = {}) {\n islandRegistry.set(name, {\n loader, // () => import('./MyComponent.js')\n mode: opts.mode || 'idle',\n media: opts.media || null,\n priority: opts.priority || 0, // Higher = hydrate first\n stores: opts.stores || [], // Shared stores this island uses\n });\n}\n\n// --- Island wrapper for SSR ---\n// Renders the static HTML with a marker the client can find.\n\nexport function Island({ name, props = {}, children, mode, priority, stores }) {\n const entry = islandRegistry.get(name);\n const resolvedMode = mode || entry?.mode || 'idle';\n const resolvedPriority = priority ?? entry?.priority ?? 0;\n const resolvedStores = stores || entry?.stores || [];\n\n // Server: render as a div with data attributes for hydration\n return {\n tag: 'div',\n props: {\n 'data-island': name,\n 'data-island-mode': resolvedMode,\n 'data-island-props': JSON.stringify(props),\n 'data-island-priority': resolvedPriority,\n 'data-island-stores': JSON.stringify(resolvedStores),\n },\n children: children || [],\n key: null,\n _vnode: true,\n };\n}\n\n// --- Priority Hydration Queue ---\n\nfunction enqueueHydration(task) {\n // Insert in priority order (higher priority first)\n let inserted = false;\n for (let i = 0; i < hydrationQueue.length; i++) {\n if (task.priority > hydrationQueue[i].priority) {\n hydrationQueue.splice(i, 0, task);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n hydrationQueue.push(task);\n }\n\n processQueue();\n}\n\nfunction processQueue() {\n if (isProcessingQueue || hydrationQueue.length === 0) return;\n isProcessingQueue = true;\n\n // Process one task at a time to avoid blocking\n const task = hydrationQueue.shift();\n\n Promise.resolve(task.hydrate())\n .catch(e => console.error('[what] Island hydration failed:', task.name, e))\n .finally(() => {\n isProcessingQueue = false;\n // Continue processing after a microtask\n queueMicrotask(processQueue);\n });\n}\n\n// Boost priority for an island (e.g., on user interaction)\nexport function boostIslandPriority(name, newPriority = 100) {\n for (const task of hydrationQueue) {\n if (task.name === name) {\n task.priority = newPriority;\n // Re-sort queue\n hydrationQueue.sort((a, b) => b.priority - a.priority);\n break;\n }\n }\n}\n\n// --- Client-side hydration ---\n\nexport function hydrateIslands() {\n // First, hydrate any shared stores from the page\n const storeScript = document.querySelector('script[data-island-stores]');\n if (storeScript) {\n hydrateIslandStores(storeScript.textContent);\n }\n\n const islands = document.querySelectorAll('[data-island]');\n\n for (const el of islands) {\n const name = el.dataset.island;\n const mode = el.dataset.islandMode || 'idle';\n const props = JSON.parse(el.dataset.islandProps || '{}');\n const priority = parseInt(el.dataset.islandPriority || '0', 10);\n const stores = JSON.parse(el.dataset.islandStores || '[]');\n const entry = islandRegistry.get(name);\n\n if (!entry) {\n console.warn(`[what] Island \"${name}\" not registered`);\n continue;\n }\n\n // Skip if already hydrated\n if (hydratedIslands.has(el)) continue;\n\n scheduleHydration(el, entry, props, mode, priority, name, stores);\n }\n}\n\nfunction scheduleHydration(el, entry, props, mode, priority, name, stores) {\n const hydrate = async () => {\n if (hydratedIslands.has(el)) return;\n hydratedIslands.add(el);\n\n const mod = await entry.loader();\n const Component = mod.default || mod;\n\n // Inject shared stores into props\n const storeProps = {};\n for (const storeName of stores) {\n storeProps[storeName] = useIslandStore(storeName);\n }\n\n // Use hydrate() to reuse server-rendered DOM instead of destroying/recreating\n const vnode = Component({ ...props, ...storeProps });\n if (el.childNodes.length > 0) {\n hydrate(vnode, el);\n } else {\n mount(vnode, el);\n }\n\n // Clean up data attributes\n el.removeAttribute('data-island');\n el.removeAttribute('data-island-mode');\n el.removeAttribute('data-island-props');\n el.removeAttribute('data-island-priority');\n el.removeAttribute('data-island-stores');\n\n // Dispatch event for analytics/debugging\n el.dispatchEvent(new CustomEvent('island:hydrated', {\n bubbles: true,\n detail: { name, mode },\n }));\n };\n\n switch (mode) {\n case 'load':\n // Immediate hydration via queue (respects priority)\n enqueueHydration({ name, priority: priority + 1000, hydrate });\n break;\n\n case 'idle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => {\n enqueueHydration({ name, priority, hydrate });\n });\n } else {\n setTimeout(() => {\n enqueueHydration({ name, priority, hydrate });\n }, 200);\n }\n break;\n\n case 'visible': {\n const observer = new IntersectionObserver((entries, obs) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n obs.disconnect();\n enqueueHydration({ name, priority, hydrate });\n break;\n }\n }\n }, { rootMargin: '200px' });\n observer.observe(el);\n break;\n }\n\n case 'media': {\n const mq = window.matchMedia(entry.media || '(max-width: 768px)');\n if (mq.matches) {\n enqueueHydration({ name, priority, hydrate });\n } else {\n mq.addEventListener('change', (e) => {\n if (e.matches) {\n enqueueHydration({ name, priority, hydrate });\n }\n }, { once: true });\n }\n break;\n }\n\n case 'action': {\n const events = ['click', 'focus', 'mouseover', 'touchstart'];\n const handler = () => {\n events.forEach(e => el.removeEventListener(e, handler));\n // Boost priority since user interacted\n enqueueHydration({ name, priority: priority + 500, hydrate });\n };\n events.forEach(e => el.addEventListener(e, handler, { once: true, passive: true }));\n break;\n }\n\n case 'static':\n // Never hydrate\n break;\n\n default:\n enqueueHydration({ name, priority, hydrate });\n }\n}\n\n// --- Auto-discover islands from data attributes ---\n// Call this once on the client to set up all islands.\n\nexport function autoIslands(registry) {\n for (const [name, config] of Object.entries(registry)) {\n island(name, config.loader || config, {\n mode: config.mode || 'idle',\n media: config.media,\n priority: config.priority || 0,\n stores: config.stores || [],\n });\n }\n\n if (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', hydrateIslands);\n } else {\n hydrateIslands();\n }\n }\n}\n\n// --- Progressive Enhancement Helpers ---\n\n// Mark an element as progressively enhanced\nexport function enhance(selector, handler) {\n if (typeof document === 'undefined') return;\n\n const elements = document.querySelectorAll(selector);\n for (const el of elements) {\n if (el.dataset.enhanced) continue;\n el.dataset.enhanced = 'true';\n handler(el);\n }\n}\n\n// Form enhancement: submit via fetch instead of page reload\nexport function enhanceForms(selector = 'form[data-enhance]') {\n enhance(selector, (form) => {\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n\n const formData = new FormData(form);\n const method = form.method.toUpperCase() || 'POST';\n const action = form.action || location.href;\n\n try {\n // Read CSRF token from meta tag\n const csrfMeta = document.querySelector('meta[name=\"csrf-token\"]') ||\n document.querySelector('meta[name=\"what-csrf-token\"]');\n const csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : null;\n\n // If no CSRF token and form hasn't opted out, block submission\n const noCsrf = form.getAttribute('data-no-csrf') === 'true';\n if (!csrfToken && !noCsrf) {\n console.warn(\n '[what] Form submission blocked: no CSRF token found. ' +\n 'Add a <meta name=\"csrf-token\"> tag or set data-no-csrf=\"true\" on the form to opt out.'\n );\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error: new Error('Missing CSRF token') },\n }));\n return;\n }\n\n const headers = {\n 'X-Requested-With': 'XMLHttpRequest',\n };\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n const response = await fetch(action, {\n method,\n body: method === 'GET' ? undefined : formData,\n headers,\n });\n\n form.dispatchEvent(new CustomEvent('form:response', {\n bubbles: true,\n detail: { response, ok: response.ok },\n }));\n } catch (error) {\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error },\n }));\n }\n });\n });\n}\n\n// --- Debugging ---\n\nexport function getIslandStatus() {\n const status = {\n registered: [...islandRegistry.keys()],\n hydrated: hydratedIslands.size,\n pending: hydrationQueue.length,\n queue: hydrationQueue.map(t => ({ name: t.name, priority: t.priority })),\n stores: [...sharedStores.keys()],\n };\n return status;\n}\n"],
|
|
5
|
-
"mappings": "AAkBA,OAAS,SAAAA,EAAgB,UAAAC,EAAQ,SAAAC,MAAa,YAE9C,IAAMC,EAAiB,IAAI,IACrBC,EAAkB,IAAI,IACtBC,EAAiB,CAAC,EACpBC,EAAoB,GAKlBC,EAAe,IAAI,IAElB,SAASC,EAAkBC,EAAMC,EAAc,CACpD,GAAIH,EAAa,IAAIE,CAAI,EACvB,OAAOF,EAAa,IAAIE,CAAI,EAG9B,IAAME,EAAQ,CAAC,EACTC,EAAU,CAAC,EAGjB,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQJ,CAAY,EACpDE,EAAQC,CAAG,EAAIZ,EAAOa,CAAK,EAC3B,OAAO,eAAeH,EAAOE,EAAK,CAChC,IAAK,IAAMD,EAAQC,CAAG,EAAE,EACxB,IAAME,GAAQH,EAAQC,CAAG,EAAE,IAAIE,CAAG,EAClC,WAAY,EACd,CAAC,EAIH,OAAAJ,EAAM,SAAWC,EACjBD,EAAM,WAAa,CAACE,EAAKG,IAAO,CAC9B,GAAIJ,EAAQC,CAAG,EACb,OAAOD,EAAQC,CAAG,EAAE,UAAUG,CAAE,CAEpC,EACAL,EAAM,OAAUK,GAAOd,EAAMc,CAAE,EAC/BL,EAAM,aAAe,IAAM,CACzB,IAAMM,EAAW,CAAC,EAClB,OAAW,CAACJ,EAAKK,CAAG,IAAK,OAAO,QAAQN,CAAO,EAC7CK,EAASJ,CAAG,EAAIK,EAAI,KAAK,EAE3B,OAAOD,CACT,EACAN,EAAM,SAAYQ,GAAS,CACzBjB,EAAM,IAAM,CACV,OAAW,CAACW,EAAKC,CAAK,IAAK,OAAO,QAAQK,CAAI,EACxCP,EAAQC,CAAG,GACbD,EAAQC,CAAG,EAAE,IAAIC,CAAK,CAG5B,CAAC,CACH,EAEAP,EAAa,IAAIE,EAAME,CAAK,EACrBA,CACT,CAGO,SAASS,EAAeX,EAAMY,EAAkB,CAAC,EAAG,CACzD,OAAId,EAAa,IAAIE,CAAI,EAChBF,EAAa,IAAIE,CAAI,EAEvBD,EAAkBC,EAAMY,CAAe,CAChD,CAGO,SAASC,GAAwB,CACtC,IAAMH,EAAO,CAAC,EACd,OAAW,CAACV,EAAME,CAAK,IAAKJ,EAC1BY,EAAKV,CAAI,EAAIE,EAAM,aAAa,EAElC,
|
|
6
|
-
"names": ["mount", "signal", "batch", "islandRegistry", "hydratedIslands", "hydrationQueue", "isProcessingQueue", "sharedStores", "createIslandStore", "name", "initialState", "store", "signals", "key", "value", "val", "fn", "snapshot", "sig", "data", "useIslandStore", "fallbackInitial", "serializeIslandStores", "hydrateIslandStores", "serialized", "storeData", "e", "island", "loader", "opts", "Island", "props", "children", "mode", "priority", "stores", "entry", "resolvedMode", "resolvedPriority", "resolvedStores", "enqueueHydration", "task", "inserted", "i", "processQueue", "boostIslandPriority", "newPriority", "a", "b", "hydrateIslands", "storeScript", "islands", "el", "scheduleHydration", "hydrate", "mod", "Component", "storeProps", "storeName", "vnode", "entries", "obs", "mq", "events", "handler", "autoIslands", "registry", "config", "enhance", "selector", "elements", "enhanceForms", "form", "formData", "method", "action", "csrfMeta", "csrfToken", "noCsrf", "headers", "response", "error", "getIslandStatus", "t"]
|
|
4
|
+
"sourcesContent": ["// What Framework - Islands Architecture\n// Each interactive piece of the page is an \"island\" \u2014 a self-contained\n// component that hydrates independently. The rest is static HTML.\n//\n// Features:\n// - Multiple hydration modes (load, idle, visible, action, media, static)\n// - Shared state across islands\n// - Priority-based hydration queue\n// - Progressive enhancement\n//\n// Modes:\n// 'static' - No JS shipped. Pure HTML. (nav, footer, etc.)\n// 'idle' - Hydrate when browser is idle (requestIdleCallback)\n// 'visible' - Hydrate when scrolled into view (IntersectionObserver)\n// 'load' - Hydrate immediately on page load\n// 'media' - Hydrate when media query matches (e.g., mobile-only)\n// 'action' - Hydrate on first user interaction (click, focus, hover)\n\nimport { mount, hydrate, signal, batch } from 'what-core';\n\nconst islandRegistry = new Map();\nconst hydratedIslands = new Set();\nconst hydrationQueue = [];\nlet isProcessingQueue = false;\n\n// --- Shared Island State ---\n// Global reactive store that persists across islands and page navigations\n\nconst sharedStores = new Map();\n\nexport function createIslandStore(name, initialState) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n\n const store = {};\n const signals = {};\n\n // Create signals for each key in initial state\n for (const [key, value] of Object.entries(initialState)) {\n signals[key] = signal(value);\n Object.defineProperty(store, key, {\n get: () => signals[key](),\n set: (val) => signals[key].set(val),\n enumerable: true,\n });\n }\n\n // Methods to interact with store\n store._signals = signals;\n store._subscribe = (key, fn) => {\n if (signals[key]) {\n return signals[key].subscribe(fn);\n }\n };\n store._batch = (fn) => batch(fn);\n store._getSnapshot = () => {\n const snapshot = {};\n for (const [key, sig] of Object.entries(signals)) {\n snapshot[key] = sig.peek();\n }\n return snapshot;\n };\n store._hydrate = (data) => {\n batch(() => {\n for (const [key, value] of Object.entries(data)) {\n if (signals[key]) {\n signals[key].set(value);\n }\n }\n });\n };\n\n sharedStores.set(name, store);\n return store;\n}\n\n// Get or create a shared store\nexport function useIslandStore(name, fallbackInitial = {}) {\n if (sharedStores.has(name)) {\n return sharedStores.get(name);\n }\n return createIslandStore(name, fallbackInitial);\n}\n\n// Serialize all shared stores for SSR\nexport function serializeIslandStores() {\n const data = {};\n for (const [name, store] of sharedStores) {\n data[name] = store._getSnapshot();\n }\n return escapeJsonForScript(JSON.stringify(data));\n}\n\nfunction escapeJsonForScript(json) {\n return json\n .replace(/</g, '\\\\u003C')\n .replace(/>/g, '\\\\u003E')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n// Hydrate shared stores from SSR data\nexport function hydrateIslandStores(serialized) {\n try {\n const data = typeof serialized === 'string' ? JSON.parse(serialized) : serialized;\n for (const [name, storeData] of Object.entries(data)) {\n const store = useIslandStore(name, storeData);\n store._hydrate(storeData);\n }\n } catch (e) {\n console.warn('[what] Failed to hydrate island stores:', e);\n }\n}\n\n// --- Register an island component ---\n\nexport function island(name, loader, opts = {}) {\n islandRegistry.set(name, {\n loader, // () => import('./MyComponent.js')\n mode: opts.mode || 'idle',\n media: opts.media || null,\n priority: opts.priority || 0, // Higher = hydrate first\n stores: opts.stores || [], // Shared stores this island uses\n });\n}\n\n// --- Island wrapper for SSR ---\n// Renders the static HTML with a marker the client can find.\n\nexport function Island({ name, props = {}, children, mode, priority, stores }) {\n const entry = islandRegistry.get(name);\n const resolvedMode = mode || entry?.mode || 'idle';\n const resolvedPriority = priority ?? entry?.priority ?? 0;\n const resolvedStores = stores || entry?.stores || [];\n\n // Server: render as a div with data attributes for hydration\n return {\n tag: 'div',\n props: {\n 'data-island': name,\n 'data-island-mode': resolvedMode,\n 'data-island-props': JSON.stringify(props),\n 'data-island-priority': resolvedPriority,\n 'data-island-stores': JSON.stringify(resolvedStores),\n },\n children: children || [],\n key: null,\n _vnode: true,\n };\n}\n\n// --- Priority Hydration Queue ---\n\nfunction enqueueHydration(task) {\n // Insert in priority order (higher priority first)\n let inserted = false;\n for (let i = 0; i < hydrationQueue.length; i++) {\n if (task.priority > hydrationQueue[i].priority) {\n hydrationQueue.splice(i, 0, task);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n hydrationQueue.push(task);\n }\n\n processQueue();\n}\n\nfunction processQueue() {\n if (isProcessingQueue || hydrationQueue.length === 0) return;\n isProcessingQueue = true;\n\n // Process one task at a time to avoid blocking\n const task = hydrationQueue.shift();\n\n Promise.resolve(task.hydrate())\n .catch(e => console.error('[what] Island hydration failed:', task.name, e))\n .finally(() => {\n isProcessingQueue = false;\n // Continue processing after a microtask\n queueMicrotask(processQueue);\n });\n}\n\n// Boost priority for an island (e.g., on user interaction)\nexport function boostIslandPriority(name, newPriority = 100) {\n for (const task of hydrationQueue) {\n if (task.name === name) {\n task.priority = newPriority;\n // Re-sort queue\n hydrationQueue.sort((a, b) => b.priority - a.priority);\n break;\n }\n }\n}\n\n// --- Client-side hydration ---\n\nexport function hydrateIslands() {\n // First, hydrate any shared stores from the page\n const storeScript = document.querySelector('script[data-island-stores]');\n if (storeScript) {\n hydrateIslandStores(storeScript.textContent);\n }\n\n const islands = document.querySelectorAll('[data-island]');\n\n for (const el of islands) {\n const name = el.dataset.island;\n const mode = el.dataset.islandMode || 'idle';\n const props = JSON.parse(el.dataset.islandProps || '{}');\n const priority = parseInt(el.dataset.islandPriority || '0', 10);\n const stores = JSON.parse(el.dataset.islandStores || '[]');\n const entry = islandRegistry.get(name);\n\n if (!entry) {\n console.warn(`[what] Island \"${name}\" not registered`);\n continue;\n }\n\n // Skip if already hydrated\n if (hydratedIslands.has(el)) continue;\n\n scheduleHydration(el, entry, props, mode, priority, name, stores);\n }\n}\n\nfunction scheduleHydration(el, entry, props, mode, priority, name, stores) {\n const hydrate = async () => {\n if (hydratedIslands.has(el)) return;\n hydratedIslands.add(el);\n\n const mod = await entry.loader();\n const Component = mod.default || mod;\n\n // Inject shared stores into props\n const storeProps = {};\n for (const storeName of stores) {\n storeProps[storeName] = useIslandStore(storeName);\n }\n\n // Use hydrate() to reuse server-rendered DOM instead of destroying/recreating\n const vnode = Component({ ...props, ...storeProps });\n if (el.childNodes.length > 0) {\n hydrate(vnode, el);\n } else {\n mount(vnode, el);\n }\n\n // Clean up data attributes\n el.removeAttribute('data-island');\n el.removeAttribute('data-island-mode');\n el.removeAttribute('data-island-props');\n el.removeAttribute('data-island-priority');\n el.removeAttribute('data-island-stores');\n\n // Dispatch event for analytics/debugging\n el.dispatchEvent(new CustomEvent('island:hydrated', {\n bubbles: true,\n detail: { name, mode },\n }));\n };\n\n switch (mode) {\n case 'load':\n // Immediate hydration via queue (respects priority)\n enqueueHydration({ name, priority: priority + 1000, hydrate });\n break;\n\n case 'idle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => {\n enqueueHydration({ name, priority, hydrate });\n });\n } else {\n setTimeout(() => {\n enqueueHydration({ name, priority, hydrate });\n }, 200);\n }\n break;\n\n case 'visible': {\n const observer = new IntersectionObserver((entries, obs) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n obs.disconnect();\n enqueueHydration({ name, priority, hydrate });\n break;\n }\n }\n }, { rootMargin: '200px' });\n observer.observe(el);\n break;\n }\n\n case 'media': {\n const mq = window.matchMedia(entry.media || '(max-width: 768px)');\n if (mq.matches) {\n enqueueHydration({ name, priority, hydrate });\n } else {\n mq.addEventListener('change', (e) => {\n if (e.matches) {\n enqueueHydration({ name, priority, hydrate });\n }\n }, { once: true });\n }\n break;\n }\n\n case 'action': {\n const events = ['click', 'focus', 'mouseover', 'touchstart'];\n const handler = () => {\n events.forEach(e => el.removeEventListener(e, handler));\n // Boost priority since user interacted\n enqueueHydration({ name, priority: priority + 500, hydrate });\n };\n events.forEach(e => el.addEventListener(e, handler, { once: true, passive: true }));\n break;\n }\n\n case 'static':\n // Never hydrate\n break;\n\n default:\n enqueueHydration({ name, priority, hydrate });\n }\n}\n\n// --- Auto-discover islands from data attributes ---\n// Call this once on the client to set up all islands.\n\nexport function autoIslands(registry) {\n for (const [name, config] of Object.entries(registry)) {\n island(name, config.loader || config, {\n mode: config.mode || 'idle',\n media: config.media,\n priority: config.priority || 0,\n stores: config.stores || [],\n });\n }\n\n if (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', hydrateIslands);\n } else {\n hydrateIslands();\n }\n }\n}\n\n// --- Progressive Enhancement Helpers ---\n\n// Mark an element as progressively enhanced\nexport function enhance(selector, handler) {\n if (typeof document === 'undefined') return;\n\n const elements = document.querySelectorAll(selector);\n for (const el of elements) {\n if (el.dataset.enhanced) continue;\n el.dataset.enhanced = 'true';\n handler(el);\n }\n}\n\n// Form enhancement: submit via fetch instead of page reload\nexport function enhanceForms(selector = 'form[data-enhance]') {\n enhance(selector, (form) => {\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n\n const formData = new FormData(form);\n const method = form.method.toUpperCase() || 'POST';\n const action = form.action || location.href;\n\n try {\n // Read CSRF token from meta tag\n const csrfMeta = document.querySelector('meta[name=\"csrf-token\"]') ||\n document.querySelector('meta[name=\"what-csrf-token\"]');\n const csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : null;\n\n // If no CSRF token and form hasn't opted out, block submission\n const noCsrf = form.getAttribute('data-no-csrf') === 'true';\n if (!csrfToken && !noCsrf) {\n console.warn(\n '[what] Form submission blocked: no CSRF token found. ' +\n 'Add a <meta name=\"csrf-token\"> tag or set data-no-csrf=\"true\" on the form to opt out.'\n );\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error: new Error('Missing CSRF token') },\n }));\n return;\n }\n\n const headers = {\n 'X-Requested-With': 'XMLHttpRequest',\n };\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n const response = await fetch(action, {\n method,\n body: method === 'GET' ? undefined : formData,\n headers,\n });\n\n form.dispatchEvent(new CustomEvent('form:response', {\n bubbles: true,\n detail: { response, ok: response.ok },\n }));\n } catch (error) {\n form.dispatchEvent(new CustomEvent('form:error', {\n bubbles: true,\n detail: { error },\n }));\n }\n });\n });\n}\n\n// --- Debugging ---\n\nexport function getIslandStatus() {\n const status = {\n registered: [...islandRegistry.keys()],\n hydrated: hydratedIslands.size,\n pending: hydrationQueue.length,\n queue: hydrationQueue.map(t => ({ name: t.name, priority: t.priority })),\n stores: [...sharedStores.keys()],\n };\n return status;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["mount", "signal", "batch", "islandRegistry", "hydratedIslands", "hydrationQueue", "isProcessingQueue", "sharedStores", "createIslandStore", "name", "initialState", "store", "signals", "key", "value", "val", "fn", "snapshot", "sig", "data", "useIslandStore", "fallbackInitial", "serializeIslandStores", "escapeJsonForScript", "json", "hydrateIslandStores", "serialized", "storeData", "e", "island", "loader", "opts", "Island", "props", "children", "mode", "priority", "stores", "entry", "resolvedMode", "resolvedPriority", "resolvedStores", "enqueueHydration", "task", "inserted", "i", "processQueue", "boostIslandPriority", "newPriority", "a", "b", "hydrateIslands", "storeScript", "islands", "el", "scheduleHydration", "hydrate", "mod", "Component", "storeProps", "storeName", "vnode", "entries", "obs", "mq", "events", "handler", "autoIslands", "registry", "config", "enhance", "selector", "elements", "enhanceForms", "form", "formData", "method", "action", "csrfMeta", "csrfToken", "noCsrf", "headers", "response", "error", "getIslandStatus", "t"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "What Framework - SSR, islands architecture, static generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"author": "ZVN DEV (https://zvndev.com)",
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"what-core": "^0.6.
|
|
39
|
+
"what-core": "^0.6.6"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -429,6 +429,16 @@ function renderAttrs(props) {
|
|
|
429
429
|
if (key.startsWith('on') && key.length > 2) continue; // Skip event handlers in SSR
|
|
430
430
|
if (val === false || val == null) continue;
|
|
431
431
|
|
|
432
|
+
const attrName = getHtmlAttributeName(key);
|
|
433
|
+
if (!isValidHtmlAttributeName(attrName)) {
|
|
434
|
+
if (_isDevMode) console.warn(`[what-server] Omitted invalid attribute name: ${key}`);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (!isSafeUrlAttributeValue(attrName, val)) {
|
|
438
|
+
if (_isDevMode) console.warn(`[what-server] Omitted unsafe URL attribute "${attrName}": ${val}`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
432
442
|
if (key === 'className' || key === 'class') {
|
|
433
443
|
out += ` class="${escapeHtml(String(val))}"`;
|
|
434
444
|
} else if (key === 'style' && typeof val === 'object') {
|
|
@@ -438,19 +448,64 @@ function renderAttrs(props) {
|
|
|
438
448
|
out += ` style="${escapeHtml(css)}"`;
|
|
439
449
|
} else if (val === true) {
|
|
440
450
|
// ARIA attributes require explicit ="true", HTML boolean attrs can be bare
|
|
441
|
-
if (
|
|
442
|
-
out += ` ${
|
|
451
|
+
if (attrName.startsWith('aria-') || attrName === 'role') {
|
|
452
|
+
out += ` ${attrName}="true"`;
|
|
443
453
|
} else {
|
|
444
|
-
out += ` ${
|
|
454
|
+
out += ` ${attrName}`;
|
|
445
455
|
}
|
|
446
456
|
} else {
|
|
447
|
-
out += ` ${
|
|
457
|
+
out += ` ${attrName}="${escapeHtml(String(val))}"`;
|
|
448
458
|
}
|
|
449
459
|
}
|
|
450
460
|
|
|
451
461
|
return out;
|
|
452
462
|
}
|
|
453
463
|
|
|
464
|
+
function getHtmlAttributeName(name) {
|
|
465
|
+
if (name === 'className') return 'class';
|
|
466
|
+
if (name === 'htmlFor') return 'for';
|
|
467
|
+
return name;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function isValidHtmlAttributeName(name) {
|
|
471
|
+
return /^[^\s"'>/=\x00-\x1f\x7f]+$/.test(name);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const URL_ATTRS = new Set([
|
|
475
|
+
'href',
|
|
476
|
+
'src',
|
|
477
|
+
'action',
|
|
478
|
+
'formaction',
|
|
479
|
+
'poster',
|
|
480
|
+
'cite',
|
|
481
|
+
'background',
|
|
482
|
+
'xlink:href',
|
|
483
|
+
]);
|
|
484
|
+
const URL_LIST_ATTRS = new Set(['srcset']);
|
|
485
|
+
|
|
486
|
+
function isSafeUrlAttributeValue(name, value) {
|
|
487
|
+
const normalizedName = String(name).toLowerCase();
|
|
488
|
+
if (URL_LIST_ATTRS.has(normalizedName)) return isSafeSrcsetValue(value);
|
|
489
|
+
if (URL_ATTRS.has(normalizedName)) return isSafeUrlValue(value);
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function isSafeUrlValue(value) {
|
|
494
|
+
if (typeof value !== 'string') return true;
|
|
495
|
+
const normalized = value.trim().replace(/[\s\x00-\x1f\x7f]/g, '').toLowerCase();
|
|
496
|
+
return !(normalized.startsWith('javascript:') || normalized.startsWith('data:') || normalized.startsWith('vbscript:'));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function isSafeSrcsetValue(value) {
|
|
500
|
+
if (typeof value !== 'string') return true;
|
|
501
|
+
return value
|
|
502
|
+
.split(',')
|
|
503
|
+
.every(candidate => {
|
|
504
|
+
const url = candidate.trim().split(/\s+/, 1)[0] || '';
|
|
505
|
+
return url === '' || isSafeUrlValue(url);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
454
509
|
function escapeHtml(str) {
|
|
455
510
|
return str
|
|
456
511
|
.replace(/&/g, '&')
|
package/src/islands.js
CHANGED
|
@@ -89,7 +89,16 @@ export function serializeIslandStores() {
|
|
|
89
89
|
for (const [name, store] of sharedStores) {
|
|
90
90
|
data[name] = store._getSnapshot();
|
|
91
91
|
}
|
|
92
|
-
return JSON.stringify(data);
|
|
92
|
+
return escapeJsonForScript(JSON.stringify(data));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function escapeJsonForScript(json) {
|
|
96
|
+
return json
|
|
97
|
+
.replace(/</g, '\\u003C')
|
|
98
|
+
.replace(/>/g, '\\u003E')
|
|
99
|
+
.replace(/&/g, '\\u0026')
|
|
100
|
+
.replace(/\u2028/g, '\\u2028')
|
|
101
|
+
.replace(/\u2029/g, '\\u2029');
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
// Hydrate shared stores from SSR data
|