@@ -212,5 +212,12 @@ // src/adapter/aws-lambda/handler.ts | ||
| this.getCookies(event, headers); | ||
| if (event.multiValueHeaders) { | ||
| for (const [k, values] of Object.entries(event.multiValueHeaders)) { | ||
| if (values) { | ||
| values.forEach((v) => headers.append(k, sanitizeHeaderValue(v))); | ||
| } | ||
| } | ||
| } | ||
| if (event.headers) { | ||
| for (const [k, v] of Object.entries(event.headers)) { | ||
| if (v) { | ||
| if (v && !headers.has(k)) { | ||
| headers.set(k, sanitizeHeaderValue(v)); | ||
@@ -220,13 +227,2 @@ } | ||
| } | ||
| if (event.multiValueHeaders) { | ||
| for (const [k, values] of Object.entries(event.multiValueHeaders)) { | ||
| if (values) { | ||
| const foundK = headers.get(k); | ||
| values.forEach((v) => { | ||
| const sanitizedValue = sanitizeHeaderValue(v); | ||
| return (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return headers; | ||
@@ -308,7 +304,3 @@ } | ||
| if (values) { | ||
| const foundK = headers.get(k); | ||
| values.forEach((v) => { | ||
| const sanitizedValue = sanitizeHeaderValue(v); | ||
| return (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue); | ||
| }); | ||
| values.forEach((v) => headers.append(k, sanitizeHeaderValue(v))); | ||
| } | ||
@@ -315,0 +307,0 @@ } |
@@ -242,5 +242,12 @@ var __defProp = Object.defineProperty; | ||
| this.getCookies(event, headers); | ||
| if (event.multiValueHeaders) { | ||
| for (const [k, values] of Object.entries(event.multiValueHeaders)) { | ||
| if (values) { | ||
| values.forEach((v) => headers.append(k, sanitizeHeaderValue(v))); | ||
| } | ||
| } | ||
| } | ||
| if (event.headers) { | ||
| for (const [k, v] of Object.entries(event.headers)) { | ||
| if (v) { | ||
| if (v && !headers.has(k)) { | ||
| headers.set(k, sanitizeHeaderValue(v)); | ||
@@ -250,13 +257,2 @@ } | ||
| } | ||
| if (event.multiValueHeaders) { | ||
| for (const [k, values] of Object.entries(event.multiValueHeaders)) { | ||
| if (values) { | ||
| const foundK = headers.get(k); | ||
| values.forEach((v) => { | ||
| const sanitizedValue = sanitizeHeaderValue(v); | ||
| return (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return headers; | ||
@@ -338,7 +334,3 @@ } | ||
| if (values) { | ||
| const foundK = headers.get(k); | ||
| values.forEach((v) => { | ||
| const sanitizedValue = sanitizeHeaderValue(v); | ||
| return (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue); | ||
| }); | ||
| values.forEach((v) => headers.append(k, sanitizeHeaderValue(v))); | ||
| } | ||
@@ -345,0 +337,0 @@ } |
@@ -64,2 +64,3 @@ var __defProp = Object.defineProperty; | ||
| const isValidClassName = (name) => /^-?[_a-zA-Z][_a-zA-Z0-9-]*$/.test(name); | ||
| const hasUnsafeSelectorChar = (name) => /[<{}]/.test(name); | ||
| const RESERVED_KEYFRAME_NAMES = /* @__PURE__ */ new Set([ | ||
@@ -202,3 +203,4 @@ "default", | ||
| [SELECTORS]: [], | ||
| [EXTERNAL_CLASS_NAMES]: [arg] | ||
| // drop names that are unsafe to emit as a selector | ||
| [EXTERNAL_CLASS_NAMES]: hasUnsafeSelectorChar(arg) ? [] : [arg] | ||
| }; | ||
@@ -205,0 +207,0 @@ } |
@@ -32,2 +32,3 @@ var __defProp = Object.defineProperty; | ||
| var import_css = require("../../jsx/dom/css"); | ||
| var import_html2 = require("../../utils/html"); | ||
| var import_common = require("./common"); | ||
@@ -92,3 +93,10 @@ var import_common2 = require("./common"); | ||
| }; | ||
| const className = new String(cssClassName[import_common.CLASS_NAME]); | ||
| const rawClassName = cssClassName[import_common.CLASS_NAME]; | ||
| let escapedClassName = rawClassName; | ||
| if (/[&<>'"]/.test(rawClassName)) { | ||
| const escapedBuffer = [""]; | ||
| (0, import_html2.escapeToBuffer)(rawClassName, escapedBuffer); | ||
| escapedClassName = escapedBuffer[0]; | ||
| } | ||
| const className = new String(escapedClassName); | ||
| Object.assign(className, cssClassName); | ||
@@ -95,0 +103,0 @@ className.isEscaped = true; |
+8
-14
@@ -128,3 +128,3 @@ var __create = Object.create; | ||
| isEscaped = true; | ||
| localContexts; | ||
| suspendedContext; | ||
| constructor(tag, props, children) { | ||
@@ -147,14 +147,8 @@ if (typeof tag !== "function" && !(0, import_utils.isValidTagName)(tag)) { | ||
| toString() { | ||
| const buffer = [""]; | ||
| this.localContexts?.forEach(([context, value]) => { | ||
| context.values.push(value); | ||
| }); | ||
| try { | ||
| const render = () => { | ||
| const buffer = [""]; | ||
| this.toStringToBuffer(buffer); | ||
| } finally { | ||
| this.localContexts?.forEach(([context]) => { | ||
| context.values.pop(); | ||
| }); | ||
| } | ||
| return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html2.resolveCallbackSync)((0, import_html.raw)(buffer[0], buffer.callbacks)).toString() : buffer[0] : (0, import_html2.stringBufferToString)(buffer, buffer.callbacks); | ||
| return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html2.resolveCallbackSync)((0, import_html.raw)(buffer[0], buffer.callbacks)).toString() : buffer[0] : (0, import_html2.stringBufferToString)(buffer, buffer.callbacks); | ||
| }; | ||
| return this.suspendedContext ? this.suspendedContext(render) : (0, import_context.runWithRenderContext)(render); | ||
| } | ||
@@ -235,3 +229,3 @@ toStringToBuffer(buffer) { | ||
| } else { | ||
| const currentContexts = import_context.globalContexts.map((c) => [c, c.values.at(-1)]); | ||
| const suspendedContext = (0, import_context.captureRenderContext)(); | ||
| buffer.unshift( | ||
@@ -241,3 +235,3 @@ "", | ||
| if (childRes instanceof JSXNode) { | ||
| childRes.localContexts = currentContexts; | ||
| childRes.suspendedContext = suspendedContext; | ||
| } | ||
@@ -244,0 +238,0 @@ return childRes; |
@@ -37,4 +37,5 @@ var __defProp = Object.defineProperty; | ||
| if (e instanceof Promise) { | ||
| const resume = (0, import_context.captureRenderContext)(); | ||
| await e; | ||
| return childrenToString(children); | ||
| return resume(() => childrenToString(children)); | ||
| } else { | ||
@@ -67,18 +68,33 @@ throw e; | ||
| const nonce = (0, import_context.useContext)(import_streaming.StreamingContext)?.scriptNonce; | ||
| let fallbackStr; | ||
| const resolveFallbackStr = async () => { | ||
| let resume; | ||
| const getResume = () => resume ||= (0, import_context.captureRenderContext)(); | ||
| let fallbackStrPromise; | ||
| const resolveFallbackStr = () => fallbackStrPromise ||= (async () => { | ||
| const awaitedFallback = await fallback; | ||
| if (typeof awaitedFallback === "string") { | ||
| fallbackStr = awaitedFallback; | ||
| return awaitedFallback; | ||
| } else { | ||
| fallbackStr = await awaitedFallback?.toString(); | ||
| if (typeof fallbackStr === "string") { | ||
| fallbackStr = (0, import_html.raw)(fallbackStr); | ||
| const fallbackResult = await getResume()(() => awaitedFallback?.toString()); | ||
| if (typeof fallbackResult === "string") { | ||
| return (0, import_html.raw)( | ||
| fallbackResult, | ||
| fallbackResult.callbacks || awaitedFallback?.callbacks | ||
| ); | ||
| } | ||
| } | ||
| })(); | ||
| const renderFallback = async (error) => { | ||
| const fallbackStr = await resolveFallbackStr(); | ||
| return getResume()(async () => { | ||
| onError?.(error); | ||
| const fallbackRes = fallbackStr !== void 0 ? fallbackStr : fallbackRender && (0, import_base.jsx)(import_base.Fragment, {}, fallbackRender(error)) || ""; | ||
| const fallbackResString = await (0, import_base.Fragment)({ | ||
| children: fallbackRes | ||
| }).toString(); | ||
| return (0, import_html.raw)( | ||
| fallbackResString, | ||
| fallbackResString.callbacks || fallbackRes.callbacks | ||
| ); | ||
| }); | ||
| }; | ||
| const fallbackRes = (error) => { | ||
| onError?.(error); | ||
| return fallbackStr || fallbackRender && (0, import_base.jsx)(import_base.Fragment, {}, fallbackRender(error)) || ""; | ||
| }; | ||
| let resArray = []; | ||
@@ -88,16 +104,16 @@ try { | ||
| } catch (e) { | ||
| await resolveFallbackStr(); | ||
| const resume2 = getResume(); | ||
| if (e instanceof Promise) { | ||
| resArray = [ | ||
| e.then(() => childrenToString(children)).catch((e2) => fallbackRes(e2)) | ||
| e.then(() => resume2(() => childrenToString(children))).catch((e2) => renderFallback(e2)) | ||
| ]; | ||
| } else { | ||
| resArray = [fallbackRes(e)]; | ||
| resArray = [await renderFallback(e)]; | ||
| } | ||
| } | ||
| if (resArray.some((res) => res instanceof Promise)) { | ||
| await resolveFallbackStr(); | ||
| getResume(); | ||
| const index = errorBoundaryCounter++; | ||
| const replaceRe = RegExp(`(<template id="E:${index}"></template>.*?)(.*?)(<!--E:${index}-->)`); | ||
| const caught = false; | ||
| let caught = false; | ||
| const catchCallback = async ({ error: error2, buffer }) => { | ||
@@ -107,9 +123,11 @@ if (caught) { | ||
| } | ||
| const fallbackResString = await (0, import_base.Fragment)({ | ||
| children: fallbackRes(error2) | ||
| }).toString(); | ||
| caught = true; | ||
| const fallbackResString = await renderFallback(error2); | ||
| const fallbackCallbacks = fallbackResString.callbacks; | ||
| if (buffer) { | ||
| buffer[0] = buffer[0].replace(replaceRe, fallbackResString); | ||
| return fallbackCallbacks?.length ? (0, import_html.raw)("", fallbackCallbacks) : ""; | ||
| } | ||
| return buffer ? "" : `<template data-hono-target="E:${index}">${fallbackResString}</template><script> | ||
| return (0, import_html.raw)( | ||
| `<template data-hono-target="E:${index}">${fallbackResString}</template><script> | ||
| ((d,c,n) => { | ||
@@ -122,3 +140,5 @@ c=d.currentScript.previousSibling | ||
| })(document) | ||
| </script>`; | ||
| </script>`, | ||
| fallbackCallbacks | ||
| ); | ||
| }; | ||
@@ -125,0 +145,0 @@ let error; |
+131
-5
@@ -20,4 +20,6 @@ var __defProp = Object.defineProperty; | ||
| __export(context_exports, { | ||
| captureRenderContext: () => captureRenderContext, | ||
| createContext: () => createContext, | ||
| globalContexts: () => globalContexts, | ||
| runWithRenderContext: () => runWithRenderContext, | ||
| useContext: () => useContext | ||
@@ -31,6 +33,128 @@ }); | ||
| const globalContexts = []; | ||
| let alsProbed = false; | ||
| let asyncLocalStorage; | ||
| let fallbackStore; | ||
| let fallbackRendersInFlight = 0; | ||
| let warnedFallbackDefault = false; | ||
| const loadAsyncLocalStorage = () => { | ||
| if (alsProbed) { | ||
| return asyncLocalStorage; | ||
| } | ||
| alsProbed = true; | ||
| const global = globalThis; | ||
| let AsyncLocalStorage; | ||
| for (const probe of [ | ||
| // Node.js >= 20.16, Deno, Bun, Cloudflare Workers (nodejs_compat). Property | ||
| // access only, so bundlers don't statically resolve `node:async_hooks`. | ||
| () => global.process?.getBuiltinModule?.("node:async_hooks")?.AsyncLocalStorage, | ||
| // Node.js < 20.16 has no `process.getBuiltinModule`, but a CJS entrypoint | ||
| // exposes the main module's `require` here. | ||
| () => global.process?.mainModule?.require?.("node:async_hooks")?.AsyncLocalStorage | ||
| ]) { | ||
| try { | ||
| AsyncLocalStorage = probe(); | ||
| } catch { | ||
| } | ||
| if (AsyncLocalStorage) { | ||
| break; | ||
| } | ||
| } | ||
| if (AsyncLocalStorage) { | ||
| asyncLocalStorage = new AsyncLocalStorage(); | ||
| } | ||
| return asyncLocalStorage; | ||
| }; | ||
| const getCurrentStore = () => { | ||
| return loadAsyncLocalStorage()?.getStore() || fallbackStore; | ||
| }; | ||
| const warnIfStorelessAccess = () => { | ||
| if (fallbackRendersInFlight > 0 && !warnedFallbackDefault) { | ||
| warnedFallbackDefault = true; | ||
| console.warn( | ||
| "hono/jsx: AsyncLocalStorage is unavailable in this runtime, so useContext() after an await in an async component falls back to the context default value during server-side rendering. To get provided values across await boundaries, use a runtime with AsyncLocalStorage (Node.js >= 20.16, Deno, Bun, or Cloudflare Workers with the nodejs_compat flag)." | ||
| ); | ||
| } | ||
| }; | ||
| const getContextValuesIn = (store, context) => { | ||
| if (!store) { | ||
| warnIfStorelessAccess(); | ||
| return context.values; | ||
| } | ||
| let values = store.get(context); | ||
| if (!values) { | ||
| values = [context.values[0]]; | ||
| store.set(context, values); | ||
| } | ||
| return values; | ||
| }; | ||
| const readContextValueIn = (store, context) => { | ||
| if (!store) { | ||
| warnIfStorelessAccess(); | ||
| return context.values.at(-1); | ||
| } | ||
| const values = store.get(context); | ||
| return values?.length ? values.at(-1) : context.values[0]; | ||
| }; | ||
| const captureContextValues = (store) => (store ? globalContexts.filter((c) => store.has(c)) : globalContexts).map((c) => [ | ||
| c, | ||
| readContextValueIn(store, c) | ||
| ]); | ||
| const resumeWithContextValues = (callback, store, contexts) => runWithRenderContext(() => { | ||
| const currentStore = getCurrentStore(); | ||
| const valuesPerContext = contexts.map(([context, value]) => { | ||
| const values = getContextValuesIn(currentStore, context); | ||
| values.push(value); | ||
| return values; | ||
| }); | ||
| const popContextValues = () => { | ||
| valuesPerContext.forEach((values) => { | ||
| values.pop(); | ||
| }); | ||
| }; | ||
| try { | ||
| const result = callback(); | ||
| if (result instanceof Promise) { | ||
| return result.finally(popContextValues); | ||
| } | ||
| popContextValues(); | ||
| return result; | ||
| } catch (e) { | ||
| popContextValues(); | ||
| throw e; | ||
| } | ||
| }, store); | ||
| const runWithRenderContext = (callback, resumeStore) => { | ||
| if (getCurrentStore()) { | ||
| return callback(); | ||
| } | ||
| const store = resumeStore ?? /* @__PURE__ */ new WeakMap(); | ||
| const storage = loadAsyncLocalStorage(); | ||
| if (storage) { | ||
| return storage.run(store, callback); | ||
| } | ||
| fallbackStore = store; | ||
| let result; | ||
| try { | ||
| result = callback(); | ||
| } finally { | ||
| fallbackStore = void 0; | ||
| } | ||
| if (!warnedFallbackDefault && result instanceof Promise) { | ||
| fallbackRendersInFlight++; | ||
| result = result.finally(() => { | ||
| fallbackRendersInFlight--; | ||
| }); | ||
| } | ||
| return result; | ||
| }; | ||
| const captureRenderContext = () => { | ||
| const store = getCurrentStore(); | ||
| const contexts = captureContextValues(store); | ||
| return (callback) => resumeWithContextValues(callback, store, contexts); | ||
| }; | ||
| const createContext = (defaultValue) => { | ||
| const values = [defaultValue]; | ||
| const context = ((props) => { | ||
| values.push(props.value); | ||
| const contextValues = getContextValuesIn(getCurrentStore(), context); | ||
| contextValues.push(props.value); | ||
| let string; | ||
@@ -40,9 +164,9 @@ try { | ||
| } catch (e) { | ||
| values.pop(); | ||
| contextValues.pop(); | ||
| throw e; | ||
| } | ||
| if (string instanceof Promise) { | ||
| return string.finally(() => values.pop()).then((resString) => (0, import_html.raw)(resString, resString.callbacks)); | ||
| return string.finally(() => contextValues.pop()).then((resString) => (0, import_html.raw)(resString, resString.callbacks)); | ||
| } else { | ||
| values.pop(); | ||
| contextValues.pop(); | ||
| return (0, import_html.raw)(string); | ||
@@ -58,9 +182,11 @@ } | ||
| const useContext = (context) => { | ||
| return context.values.at(-1); | ||
| return readContextValueIn(getCurrentStore(), context); | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| captureRenderContext, | ||
| createContext, | ||
| globalContexts, | ||
| runWithRenderContext, | ||
| useContext | ||
| }); |
@@ -45,5 +45,4 @@ var __defProp = Object.defineProperty; | ||
| const stackNode = { [import_constants.DOM_STASH]: [0, []] }; | ||
| const popNodeStack = (value) => { | ||
| const popNodeStack = () => { | ||
| import_render.buildDataStack.pop(); | ||
| return value; | ||
| }; | ||
@@ -58,8 +57,11 @@ try { | ||
| if (e instanceof Promise) { | ||
| const resume = (0, import_context.captureRenderContext)(); | ||
| resArray = [ | ||
| e.then(() => { | ||
| stackNode[import_constants.DOM_STASH][0] = 0; | ||
| import_render.buildDataStack.push([[], stackNode]); | ||
| return (0, import_components.childrenToString)(children).then(popNodeStack); | ||
| }) | ||
| e.then( | ||
| () => resume(() => { | ||
| stackNode[import_constants.DOM_STASH][0] = 0; | ||
| import_render.buildDataStack.push([[], stackNode]); | ||
| return (0, import_components.childrenToString)(children).finally(popNodeStack); | ||
| }) | ||
| ) | ||
| ]; | ||
@@ -66,0 +68,0 @@ } else { |
@@ -28,2 +28,3 @@ // src/helper/css/common.ts | ||
| var isValidClassName = (name) => /^-?[_a-zA-Z][_a-zA-Z0-9-]*$/.test(name); | ||
| var hasUnsafeSelectorChar = (name) => /[<{}]/.test(name); | ||
| var RESERVED_KEYFRAME_NAMES = /* @__PURE__ */ new Set([ | ||
@@ -166,3 +167,4 @@ "default", | ||
| [SELECTORS]: [], | ||
| [EXTERNAL_CLASS_NAMES]: [arg] | ||
| // drop names that are unsafe to emit as a selector | ||
| [EXTERNAL_CLASS_NAMES]: hasUnsafeSelectorChar(arg) ? [] : [arg] | ||
| }; | ||
@@ -169,0 +171,0 @@ } |
@@ -5,2 +5,3 @@ // src/helper/css/index.ts | ||
| import { createCssJsxDomObjects } from "../../jsx/dom/css.js"; | ||
| import { escapeToBuffer } from "../../utils/html.js"; | ||
| import { | ||
@@ -76,3 +77,10 @@ CLASS_NAME, | ||
| }; | ||
| const className = new String(cssClassName[CLASS_NAME]); | ||
| const rawClassName = cssClassName[CLASS_NAME]; | ||
| let escapedClassName = rawClassName; | ||
| if (/[&<>'"]/.test(rawClassName)) { | ||
| const escapedBuffer = [""]; | ||
| escapeToBuffer(rawClassName, escapedBuffer); | ||
| escapedClassName = escapedBuffer[0]; | ||
| } | ||
| const className = new String(escapedClassName); | ||
| Object.assign(className, cssClassName); | ||
@@ -79,0 +87,0 @@ className.isEscaped = true; |
+15
-15
@@ -5,3 +5,9 @@ // src/jsx/base.ts | ||
| import { DOM_RENDERER, DOM_MEMO } from "./constants.js"; | ||
| import { createContext, globalContexts, useContext } from "./context.js"; | ||
| import { | ||
| captureRenderContext, | ||
| createContext, | ||
| globalContexts, | ||
| runWithRenderContext, | ||
| useContext | ||
| } from "./context.js"; | ||
| import { domRenderers } from "./intrinsic-element/common.js"; | ||
@@ -92,3 +98,3 @@ import * as intrinsicElementTags from "./intrinsic-element/components.js"; | ||
| isEscaped = true; | ||
| localContexts; | ||
| suspendedContext; | ||
| constructor(tag, props, children) { | ||
@@ -111,14 +117,8 @@ if (typeof tag !== "function" && !isValidTagName(tag)) { | ||
| toString() { | ||
| const buffer = [""]; | ||
| this.localContexts?.forEach(([context, value]) => { | ||
| context.values.push(value); | ||
| }); | ||
| try { | ||
| const render = () => { | ||
| const buffer = [""]; | ||
| this.toStringToBuffer(buffer); | ||
| } finally { | ||
| this.localContexts?.forEach(([context]) => { | ||
| context.values.pop(); | ||
| }); | ||
| } | ||
| return buffer.length === 1 ? "callbacks" in buffer ? resolveCallbackSync(raw(buffer[0], buffer.callbacks)).toString() : buffer[0] : stringBufferToString(buffer, buffer.callbacks); | ||
| return buffer.length === 1 ? "callbacks" in buffer ? resolveCallbackSync(raw(buffer[0], buffer.callbacks)).toString() : buffer[0] : stringBufferToString(buffer, buffer.callbacks); | ||
| }; | ||
| return this.suspendedContext ? this.suspendedContext(render) : runWithRenderContext(render); | ||
| } | ||
@@ -199,3 +199,3 @@ toStringToBuffer(buffer) { | ||
| } else { | ||
| const currentContexts = globalContexts.map((c) => [c, c.values.at(-1)]); | ||
| const suspendedContext = captureRenderContext(); | ||
| buffer.unshift( | ||
@@ -205,3 +205,3 @@ "", | ||
| if (childRes instanceof JSXNode) { | ||
| childRes.localContexts = currentContexts; | ||
| childRes.suspendedContext = suspendedContext; | ||
| } | ||
@@ -208,0 +208,0 @@ return childRes; |
+42
-22
@@ -6,3 +6,3 @@ // src/jsx/components.ts | ||
| import { DOM_RENDERER } from "./constants.js"; | ||
| import { useContext } from "./context.js"; | ||
| import { captureRenderContext, useContext } from "./context.js"; | ||
| import { ErrorBoundary as ErrorBoundaryDomRenderer } from "./dom/components.js"; | ||
@@ -16,4 +16,5 @@ import { StreamingContext } from "./streaming.js"; | ||
| if (e instanceof Promise) { | ||
| const resume = captureRenderContext(); | ||
| await e; | ||
| return childrenToString(children); | ||
| return resume(() => childrenToString(children)); | ||
| } else { | ||
@@ -46,18 +47,33 @@ throw e; | ||
| const nonce = useContext(StreamingContext)?.scriptNonce; | ||
| let fallbackStr; | ||
| const resolveFallbackStr = async () => { | ||
| let resume; | ||
| const getResume = () => resume ||= captureRenderContext(); | ||
| let fallbackStrPromise; | ||
| const resolveFallbackStr = () => fallbackStrPromise ||= (async () => { | ||
| const awaitedFallback = await fallback; | ||
| if (typeof awaitedFallback === "string") { | ||
| fallbackStr = awaitedFallback; | ||
| return awaitedFallback; | ||
| } else { | ||
| fallbackStr = await awaitedFallback?.toString(); | ||
| if (typeof fallbackStr === "string") { | ||
| fallbackStr = raw(fallbackStr); | ||
| const fallbackResult = await getResume()(() => awaitedFallback?.toString()); | ||
| if (typeof fallbackResult === "string") { | ||
| return raw( | ||
| fallbackResult, | ||
| fallbackResult.callbacks || awaitedFallback?.callbacks | ||
| ); | ||
| } | ||
| } | ||
| })(); | ||
| const renderFallback = async (error) => { | ||
| const fallbackStr = await resolveFallbackStr(); | ||
| return getResume()(async () => { | ||
| onError?.(error); | ||
| const fallbackRes = fallbackStr !== void 0 ? fallbackStr : fallbackRender && jsx(Fragment, {}, fallbackRender(error)) || ""; | ||
| const fallbackResString = await Fragment({ | ||
| children: fallbackRes | ||
| }).toString(); | ||
| return raw( | ||
| fallbackResString, | ||
| fallbackResString.callbacks || fallbackRes.callbacks | ||
| ); | ||
| }); | ||
| }; | ||
| const fallbackRes = (error) => { | ||
| onError?.(error); | ||
| return fallbackStr || fallbackRender && jsx(Fragment, {}, fallbackRender(error)) || ""; | ||
| }; | ||
| let resArray = []; | ||
@@ -67,16 +83,16 @@ try { | ||
| } catch (e) { | ||
| await resolveFallbackStr(); | ||
| const resume2 = getResume(); | ||
| if (e instanceof Promise) { | ||
| resArray = [ | ||
| e.then(() => childrenToString(children)).catch((e2) => fallbackRes(e2)) | ||
| e.then(() => resume2(() => childrenToString(children))).catch((e2) => renderFallback(e2)) | ||
| ]; | ||
| } else { | ||
| resArray = [fallbackRes(e)]; | ||
| resArray = [await renderFallback(e)]; | ||
| } | ||
| } | ||
| if (resArray.some((res) => res instanceof Promise)) { | ||
| await resolveFallbackStr(); | ||
| getResume(); | ||
| const index = errorBoundaryCounter++; | ||
| const replaceRe = RegExp(`(<template id="E:${index}"></template>.*?)(.*?)(<!--E:${index}-->)`); | ||
| const caught = false; | ||
| let caught = false; | ||
| const catchCallback = async ({ error: error2, buffer }) => { | ||
@@ -86,9 +102,11 @@ if (caught) { | ||
| } | ||
| const fallbackResString = await Fragment({ | ||
| children: fallbackRes(error2) | ||
| }).toString(); | ||
| caught = true; | ||
| const fallbackResString = await renderFallback(error2); | ||
| const fallbackCallbacks = fallbackResString.callbacks; | ||
| if (buffer) { | ||
| buffer[0] = buffer[0].replace(replaceRe, fallbackResString); | ||
| return fallbackCallbacks?.length ? raw("", fallbackCallbacks) : ""; | ||
| } | ||
| return buffer ? "" : `<template data-hono-target="E:${index}">${fallbackResString}</template><script> | ||
| return raw( | ||
| `<template data-hono-target="E:${index}">${fallbackResString}</template><script> | ||
| ((d,c,n) => { | ||
@@ -101,3 +119,5 @@ c=d.currentScript.previousSibling | ||
| })(document) | ||
| </script>`; | ||
| </script>`, | ||
| fallbackCallbacks | ||
| ); | ||
| }; | ||
@@ -104,0 +124,0 @@ let error; |
+129
-5
@@ -7,6 +7,128 @@ // src/jsx/context.ts | ||
| var globalContexts = []; | ||
| var alsProbed = false; | ||
| var asyncLocalStorage; | ||
| var fallbackStore; | ||
| var fallbackRendersInFlight = 0; | ||
| var warnedFallbackDefault = false; | ||
| var loadAsyncLocalStorage = () => { | ||
| if (alsProbed) { | ||
| return asyncLocalStorage; | ||
| } | ||
| alsProbed = true; | ||
| const global = globalThis; | ||
| let AsyncLocalStorage; | ||
| for (const probe of [ | ||
| // Node.js >= 20.16, Deno, Bun, Cloudflare Workers (nodejs_compat). Property | ||
| // access only, so bundlers don't statically resolve `node:async_hooks`. | ||
| () => global.process?.getBuiltinModule?.("node:async_hooks")?.AsyncLocalStorage, | ||
| // Node.js < 20.16 has no `process.getBuiltinModule`, but a CJS entrypoint | ||
| // exposes the main module's `require` here. | ||
| () => global.process?.mainModule?.require?.("node:async_hooks")?.AsyncLocalStorage | ||
| ]) { | ||
| try { | ||
| AsyncLocalStorage = probe(); | ||
| } catch { | ||
| } | ||
| if (AsyncLocalStorage) { | ||
| break; | ||
| } | ||
| } | ||
| if (AsyncLocalStorage) { | ||
| asyncLocalStorage = new AsyncLocalStorage(); | ||
| } | ||
| return asyncLocalStorage; | ||
| }; | ||
| var getCurrentStore = () => { | ||
| return loadAsyncLocalStorage()?.getStore() || fallbackStore; | ||
| }; | ||
| var warnIfStorelessAccess = () => { | ||
| if (fallbackRendersInFlight > 0 && !warnedFallbackDefault) { | ||
| warnedFallbackDefault = true; | ||
| console.warn( | ||
| "hono/jsx: AsyncLocalStorage is unavailable in this runtime, so useContext() after an await in an async component falls back to the context default value during server-side rendering. To get provided values across await boundaries, use a runtime with AsyncLocalStorage (Node.js >= 20.16, Deno, Bun, or Cloudflare Workers with the nodejs_compat flag)." | ||
| ); | ||
| } | ||
| }; | ||
| var getContextValuesIn = (store, context) => { | ||
| if (!store) { | ||
| warnIfStorelessAccess(); | ||
| return context.values; | ||
| } | ||
| let values = store.get(context); | ||
| if (!values) { | ||
| values = [context.values[0]]; | ||
| store.set(context, values); | ||
| } | ||
| return values; | ||
| }; | ||
| var readContextValueIn = (store, context) => { | ||
| if (!store) { | ||
| warnIfStorelessAccess(); | ||
| return context.values.at(-1); | ||
| } | ||
| const values = store.get(context); | ||
| return values?.length ? values.at(-1) : context.values[0]; | ||
| }; | ||
| var captureContextValues = (store) => (store ? globalContexts.filter((c) => store.has(c)) : globalContexts).map((c) => [ | ||
| c, | ||
| readContextValueIn(store, c) | ||
| ]); | ||
| var resumeWithContextValues = (callback, store, contexts) => runWithRenderContext(() => { | ||
| const currentStore = getCurrentStore(); | ||
| const valuesPerContext = contexts.map(([context, value]) => { | ||
| const values = getContextValuesIn(currentStore, context); | ||
| values.push(value); | ||
| return values; | ||
| }); | ||
| const popContextValues = () => { | ||
| valuesPerContext.forEach((values) => { | ||
| values.pop(); | ||
| }); | ||
| }; | ||
| try { | ||
| const result = callback(); | ||
| if (result instanceof Promise) { | ||
| return result.finally(popContextValues); | ||
| } | ||
| popContextValues(); | ||
| return result; | ||
| } catch (e) { | ||
| popContextValues(); | ||
| throw e; | ||
| } | ||
| }, store); | ||
| var runWithRenderContext = (callback, resumeStore) => { | ||
| if (getCurrentStore()) { | ||
| return callback(); | ||
| } | ||
| const store = resumeStore ?? /* @__PURE__ */ new WeakMap(); | ||
| const storage = loadAsyncLocalStorage(); | ||
| if (storage) { | ||
| return storage.run(store, callback); | ||
| } | ||
| fallbackStore = store; | ||
| let result; | ||
| try { | ||
| result = callback(); | ||
| } finally { | ||
| fallbackStore = void 0; | ||
| } | ||
| if (!warnedFallbackDefault && result instanceof Promise) { | ||
| fallbackRendersInFlight++; | ||
| result = result.finally(() => { | ||
| fallbackRendersInFlight--; | ||
| }); | ||
| } | ||
| return result; | ||
| }; | ||
| var captureRenderContext = () => { | ||
| const store = getCurrentStore(); | ||
| const contexts = captureContextValues(store); | ||
| return (callback) => resumeWithContextValues(callback, store, contexts); | ||
| }; | ||
| var createContext = (defaultValue) => { | ||
| const values = [defaultValue]; | ||
| const context = ((props) => { | ||
| values.push(props.value); | ||
| const contextValues = getContextValuesIn(getCurrentStore(), context); | ||
| contextValues.push(props.value); | ||
| let string; | ||
@@ -16,9 +138,9 @@ try { | ||
| } catch (e) { | ||
| values.pop(); | ||
| contextValues.pop(); | ||
| throw e; | ||
| } | ||
| if (string instanceof Promise) { | ||
| return string.finally(() => values.pop()).then((resString) => raw(resString, resString.callbacks)); | ||
| return string.finally(() => contextValues.pop()).then((resString) => raw(resString, resString.callbacks)); | ||
| } else { | ||
| values.pop(); | ||
| contextValues.pop(); | ||
| return raw(string); | ||
@@ -34,8 +156,10 @@ } | ||
| var useContext = (context) => { | ||
| return context.values.at(-1); | ||
| return readContextValueIn(getCurrentStore(), context); | ||
| }; | ||
| export { | ||
| captureRenderContext, | ||
| createContext, | ||
| globalContexts, | ||
| runWithRenderContext, | ||
| useContext | ||
| }; |
@@ -7,3 +7,3 @@ // src/jsx/streaming.ts | ||
| import { DOM_RENDERER, DOM_STASH } from "./constants.js"; | ||
| import { createContext, useContext } from "./context.js"; | ||
| import { captureRenderContext, createContext, useContext } from "./context.js"; | ||
| import { Suspense as SuspenseDomRenderer } from "./dom/components.js"; | ||
@@ -23,5 +23,4 @@ import { buildDataStack } from "./dom/render.js"; | ||
| const stackNode = { [DOM_STASH]: [0, []] }; | ||
| const popNodeStack = (value) => { | ||
| const popNodeStack = () => { | ||
| buildDataStack.pop(); | ||
| return value; | ||
| }; | ||
@@ -36,8 +35,11 @@ try { | ||
| if (e instanceof Promise) { | ||
| const resume = captureRenderContext(); | ||
| resArray = [ | ||
| e.then(() => { | ||
| stackNode[DOM_STASH][0] = 0; | ||
| buildDataStack.push([[], stackNode]); | ||
| return childrenToString(children).then(popNodeStack); | ||
| }) | ||
| e.then( | ||
| () => resume(() => { | ||
| stackNode[DOM_STASH][0] = 0; | ||
| buildDataStack.push([[], stackNode]); | ||
| return childrenToString(children).finally(popNodeStack); | ||
| }) | ||
| ) | ||
| ]; | ||
@@ -44,0 +46,0 @@ } else { |
@@ -26,3 +26,2 @@ import type { HtmlEscaped, HtmlEscapedString, StringBufferWithCallbacks } from '../utils/html'; | ||
| export declare const booleanAttributes: string[]; | ||
| type LocalContexts = [Context<unknown>, unknown][]; | ||
| export type Child = string | Promise<string> | number | JSXNode | null | undefined | boolean | Child[]; | ||
@@ -35,3 +34,3 @@ export declare class JSXNode implements HtmlEscaped { | ||
| isEscaped: true; | ||
| localContexts?: LocalContexts; | ||
| suspendedContext?: <T>(callback: () => T) => T; | ||
| constructor(tag: string | Function, props: Props, children: Child[]); | ||
@@ -60,2 +59,1 @@ get type(): string | Function; | ||
| export declare const reactAPICompatVersion = "19.0.0-hono-jsx"; | ||
| export {}; |
@@ -11,3 +11,42 @@ import type { FC, PropsWithChildren } from './'; | ||
| export declare const globalContexts: Context<unknown>[]; | ||
| /** Per-render context store, isolated per request so values never leak across renders. */ | ||
| type RenderStore = WeakMap<Context<unknown>, unknown[]>; | ||
| /** | ||
| * Establish the request-scoped context store for a render. | ||
| * | ||
| * `resumeStore` continues a suspended subtree in the same store on the fallback | ||
| * path (ignored when `AsyncLocalStorage` is available, where isolation is | ||
| * automatic). | ||
| * | ||
| * Without `AsyncLocalStorage` a render can't be followed across `await`, so the | ||
| * store lives in `fallbackStore` only during synchronous work (mirroring | ||
| * React's request storage). Reading context after `await` then finds no store | ||
| * and falls back to the default value — never another request's value. | ||
| */ | ||
| export declare const runWithRenderContext: <T>(callback: () => T, resumeStore?: RenderStore) => T; | ||
| /** | ||
| * Capture the current render store and return a resumer that re-establishes it | ||
| * around a deferred continuation (e.g. a re-render after a suspended promise | ||
| * settles). Shared by every suspension point so none reimplements it. | ||
| */ | ||
| export declare const captureRenderContext: () => (<T>(callback: () => T) => T); | ||
| /** | ||
| * Create a context whose value can be provided with `<Context.Provider>` and | ||
| * read with {@link useContext}. | ||
| * | ||
| * Server-side renders are isolated per request, so a provided value never leaks | ||
| * into a concurrent request — even across `await` in an async component, when | ||
| * `AsyncLocalStorage` is available (Node.js >= 20.16, Deno, Bun, Cloudflare | ||
| * Workers with `nodejs_compat`). Without it, reading context after `await` | ||
| * returns the default value; synchronous components and `use()`-based | ||
| * suspension are unaffected. | ||
| */ | ||
| export declare const createContext: <T>(defaultValue: T) => Context<T>; | ||
| /** | ||
| * Read the current value of a context created with {@link createContext}. | ||
| * | ||
| * Safe to call from async components after `await`. See {@link createContext} | ||
| * for the per-runtime isolation guarantees. | ||
| */ | ||
| export declare const useContext: <T>(context: Context<T>) => T; | ||
| export {}; |
+1
-1
| { | ||
| "name": "hono", | ||
| "version": "4.12.26", | ||
| "version": "4.12.27", | ||
| "description": "Web framework built on Web Standards", | ||
@@ -5,0 +5,0 @@ "main": "dist/cjs/index.js", |
Sorry, the diff of this file is not supported yet
1425806
0.83%37552
0.89%