{"version":3,"sources":["../src/server/response.ts"],"sourcesContent":["import type { ServerResponse } from \"node:http\";\n\ninterface ResponseEventWatcher<T> {\n  promise: Promise<T>;\n  dispose(): void;\n}\n\n/**\n * Watch a real Node response without requiring every Node-compatible response\n * adapter or test double to extend EventEmitter. The framework only needs\n * disconnect handling when the response exposes both halves of the listener\n * lifecycle; otherwise callers can still send ordinary non-blocked bodies.\n */\nfunction watchResponseEvent<T>(\n  res: ServerResponse,\n  events: ReadonlyArray<readonly [event: string, value: T]>,\n): ResponseEventWatcher<T> | null {\n  if (typeof res.once !== \"function\" || typeof res.removeListener !== \"function\") {\n    return null;\n  }\n\n  let settled = false;\n  const listeners = events.map(([event, value]) => {\n    const listener = () => {\n      if (settled) return;\n      settled = true;\n      dispose();\n      resolvePromise(value);\n    };\n    return { event, listener };\n  });\n  let resolvePromise!: (value: T) => void;\n  const promise = new Promise<T>((resolve) => {\n    resolvePromise = resolve;\n    for (const { event, listener } of listeners) {\n      res.once(event, listener);\n    }\n  });\n  const dispose = () => {\n    for (const { event, listener } of listeners) {\n      res.removeListener(event, listener);\n    }\n  };\n\n  return { promise, dispose };\n}\n\n/**\n * Waits until the response can accept more writes. Resolves false when the\n * client is gone (close or error): a disconnected socket never emits drain,\n * so waiting on drain alone leaks the pending handler, its reader lock, and\n * the response body for the life of the process.\n */\nasync function waitForWritable(res: ServerResponse): Promise<boolean> {\n  if (res.writableEnded || res.destroyed) {\n    return false;\n  }\n\n  const watcher = watchResponseEvent(res, [\n    [\"drain\", true],\n    [\"close\", false],\n    [\"error\", false],\n  ]);\n  if (!watcher) {\n    return false;\n  }\n\n  try {\n    return await watcher.promise;\n  } finally {\n    watcher.dispose();\n  }\n}\n\n/**\n * Older Fetch implementations expose repeated Set-Cookie fields as one\n * comma-joined value. Split only at a comma followed by another cookie-pair;\n * commas inside Expires dates remain part of the current cookie. RFC cookie\n * values exclude commas, so a comma followed by a cookie-pair is unambiguous\n * for valid Set-Cookie syntax once the original field boundaries are lost.\n */\nfunction splitSetCookieHeader(value: string): string[] {\n  const cookies: string[] = [];\n  let start = 0;\n\n  for (let index = 0; index < value.length; index += 1) {\n    if (value[index] !== \",\") continue;\n\n    let next = index + 1;\n    while (value[next] === \" \" || value[next] === \"\\t\") next += 1;\n\n    const equals = value.indexOf(\"=\", next);\n    if (equals === -1) continue;\n\n    const separator = value.slice(next, equals);\n    if (separator.length === 0 || /[;,\\s]/.test(separator)) continue;\n\n    cookies.push(value.slice(start, index).trim());\n    start = next;\n    index = next - 1;\n  }\n\n  cookies.push(value.slice(start).trim());\n  return cookies.filter(Boolean);\n}\n\nexport function applyWebResponseHeaders(\n  res: Pick<ServerResponse, \"setHeader\"> & Partial<Pick<ServerResponse, \"getHeader\">>,\n  headers: Headers,\n  options: { appendSetCookie?: boolean } = {},\n): void {\n  const responseHeaders = headers as Headers & {\n    getSetCookie?: () => string[];\n    raw?: () => Record<string, string[]>;\n  };\n  const rawSetCookies = responseHeaders.raw?.()[\"set-cookie\"];\n  const setCookies = responseHeaders.getSetCookie?.() || rawSetCookies || [];\n  const existing =\n    options.appendSetCookie && typeof res.getHeader === \"function\"\n      ? res.getHeader(\"Set-Cookie\")\n      : undefined;\n  const existingCookies = Array.isArray(existing)\n    ? existing.map(String)\n    : existing === undefined\n      ? []\n      : [String(existing)];\n\n  let fallbackSetCookie = \"\";\n  headers.forEach((value, key) => {\n    if (key.toLowerCase() === \"set-cookie\") {\n      fallbackSetCookie = value;\n      return;\n    }\n    res.setHeader(key, value);\n  });\n\n  const cookies =\n    setCookies.length > 0\n      ? setCookies\n      : fallbackSetCookie\n        ? splitSetCookieHeader(fallbackSetCookie)\n        : [];\n  if (cookies.length > 0) {\n    res.setHeader(\"Set-Cookie\", [...existingCookies, ...cookies]);\n  }\n}\n\nexport async function sendWebResponse(res: ServerResponse, response: Response): Promise<void> {\n  res.statusCode = response.status;\n  applyWebResponseHeaders(res, response.headers, { appendSetCookie: true });\n\n  if (!response.body) {\n    res.end();\n    return;\n  }\n\n  if (typeof res.write !== \"function\") {\n    const body = await response.arrayBuffer();\n    res.end(Buffer.from(body));\n    return;\n  }\n\n  const reader = response.body.getReader();\n  const disconnectWatcher = watchResponseEvent(res, [\n    [\"close\", true],\n    [\"error\", true],\n  ]);\n\n  try {\n    while (true) {\n      if (res.destroyed) {\n        // The client disconnected mid-response; drop the rest of the body so\n        // the handler can return.\n        void reader.cancel().catch(() => {});\n        return;\n      }\n\n      const read = reader.read().then((result) => ({ type: \"read\" as const, result }));\n      const next = disconnectWatcher\n        ? await Promise.race([\n            read,\n            disconnectWatcher.promise.then(() => ({ type: \"disconnect\" as const })),\n          ])\n        : await read;\n      if (next.type === \"disconnect\") {\n        void reader.cancel().catch(() => {});\n        return;\n      }\n\n      const { done, value } = next.result;\n      if (done) {\n        break;\n      }\n\n      if (!value || value.byteLength === 0) {\n        continue;\n      }\n\n      if (!res.write(value)) {\n        if (!(await waitForWritable(res))) {\n          void reader.cancel().catch(() => {});\n          return;\n        }\n      }\n    }\n\n    res.end();\n  } catch (error) {\n    // Releasing the lock does not stop the producer. Cancel it when the\n    // downstream write fails, without waiting on app-owned cleanup or letting\n    // a cancellation failure replace the original error.\n    void reader.cancel(error).catch(() => {});\n    if (!res.writableEnded) {\n      const responseError = error instanceof Error ? error : new Error(String(error));\n      if (typeof res.destroy === \"function\") {\n        res.destroy(responseError);\n      } else {\n        res.end();\n      }\n    }\n    throw error;\n  } finally {\n    disconnectWatcher?.dispose();\n    try {\n      reader.releaseLock();\n    } catch {\n      // A disconnect can win the race with a pending read. Cancelling the\n      // reader settles it asynchronously, so there may be no lock to release\n      // synchronously here.\n    }\n  }\n}\n"],"mappings":"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","names":[]}