UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

127 lines (119 loc) 3.99 kB
import { isResponse } from '../utils/isResponse' import { type ALSId, asyncHeadersCache, mergeHeaders, requestAsyncLocalStore, runWithAsyncLocalContext, } from './one-server-only' // lightweight monotonic id - avoids Math.random() per request let _nextId = 1 function createId(): ALSId { return { _id: _nextId++ } } export async function resolveResponse(getResponse: () => Promise<Response>) { // always read ALS from globalThis to match the bundled server code const store = requestAsyncLocalStore ?? (globalThis['__vxrnrequestAsyncLocalStore'] as typeof requestAsyncLocalStore) if (store) { const id = createId() let response: Response await store.run(id, async () => { try { response = await getResponse() response = await getResponseWithAddedHeaders(response!, id) } catch (err) { if (isResponse(err)) { response = err as Response } else { throw err } } }) return response! } // fallback for non-SSR contexts return runWithAsyncLocalContext(async (id) => { try { const response = await getResponse() return await getResponseWithAddedHeaders(response, id) } catch (err) { if (isResponse(err)) { return err as Response } throw err } }) } /** * enter ALS context once for the entire request handler. */ export function withRequestContext<T>(fn: () => Promise<T>): Promise<T> { const store = requestAsyncLocalStore if (store) { const id = createId() return store.run(id, fn) as Promise<T> } return fn() } export function resolveAPIEndpoint( // this is the result of importing the file: runEndpoint: () => Promise<any>, request: Request, params: Record<string, string> ) { return resolveResponse(async () => { const imported = await runEndpoint() const requestType = request.method || 'GET' const handler = imported[requestType] || imported.default if (!handler) { console.warn(`No handler found for request ${requestType}`) return } return await handler(request, { params }) }) } async function getResponseWithAddedHeaders(response: any, id: object) { // read from globalThis to match the bundled server code's cache instance const cache: WeakMap<any, Headers> = globalThis['__vxrnasyncHeadersCache'] ?? asyncHeadersCache const asyncHeaders = cache.get(id) if (asyncHeaders) { try { if (response instanceof Response) { // create a new response with merged headers rather than mutating in place, // because hono's compress middleware captures headers at response creation time // and won't see mutations made to response.headers after the fact. // clone first so the original body stream isn't locked/consumed // (Response.json() bodies are single-use ReadableStreams) const cloned = response.clone() const headers = new Headers() mergeHeaders(headers, cloned.headers) mergeHeaders(headers, asyncHeaders) response = new Response(cloned.body, { status: cloned.status, statusText: cloned.statusText, headers, }) } else { if (response && typeof response === 'object') { response = Response.json(response, { headers: asyncHeaders }) } else { // for string responses (like html), we need to preserve content-type // if not explicitly set in asyncHeaders const headers = new Headers(asyncHeaders) if (!headers.has('content-type')) { // assume html for string responses since that's the common case // (dev mode returns html strings from handlePage) headers.set('content-type', 'text/html') } response = new Response(response as any, { headers }) } } } catch (err) { console.error(` [one] error adding headers: ${err}`) } } return response }