{"version":3,"sources":["../src/after.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport type AfterCallback = () => void | Promise<void>;\n\n/** Runtime hooks supplied by a deployment adapter. */\nexport interface FarmAfterPlatformContext {\n  /** Keep a serverless invocation alive until the scheduled work settles. */\n  waitUntil?: (promise: Promise<void>) => void;\n  /** Register a callback that runs once the response has finished. */\n  onResponseFinished?: (callback: () => void) => void;\n}\n\ninterface AfterTask {\n  result: Promise<void>;\n  start: () => void;\n}\n\ninterface AfterRequestState {\n  completion: Promise<void>;\n  finishResponse: () => void;\n  phase: \"open\" | \"running\" | \"closed\";\n  reportError: (error: unknown) => void;\n  tasks: AfterTask[];\n}\n\nconst AFTER_STORAGE = Symbol.for(\"@farm.js/core/after-storage\");\n\nfunction getAfterStorage(): AsyncLocalStorage<AfterRequestState> {\n  const runtime = globalThis as typeof globalThis & Record<PropertyKey, unknown>;\n  const existing = runtime[AFTER_STORAGE];\n  if (existing instanceof AsyncLocalStorage) {\n    return existing as AsyncLocalStorage<AfterRequestState>;\n  }\n\n  const storage = new AsyncLocalStorage<AfterRequestState>();\n  runtime[AFTER_STORAGE] = storage;\n  return storage;\n}\n\nconst afterStorage = getAfterStorage();\n\nfunction defaultAfterErrorReporter(error: unknown): void {\n  console.error(\"[Farm.js] after() callback failed:\", error);\n}\n\nfunction reportAfterError(state: AfterRequestState, error: unknown): void {\n  try {\n    state.reportError(error);\n  } catch {\n    // Error reporting must never interrupt the remaining post-response work.\n  }\n}\n\nfunction createAfterRequestState(\n  reportError: (error: unknown) => void = defaultAfterErrorReporter,\n): AfterRequestState {\n  let resolveResponseFinished!: () => void;\n  let responseFinished = false;\n  const responseFinishedPromise = new Promise<void>((resolve) => {\n    resolveResponseFinished = resolve;\n  });\n\n  const state: AfterRequestState = {\n    completion: Promise.resolve(),\n    finishResponse: () => {\n      if (responseFinished) return;\n      responseFinished = true;\n      resolveResponseFinished();\n    },\n    phase: \"open\",\n    reportError,\n    tasks: [],\n  };\n\n  state.completion = responseFinishedPromise.then(() =>\n    afterStorage.run(state, async () => {\n      state.phase = \"running\";\n\n      for (let index = 0; index < state.tasks.length; index++) {\n        const task = state.tasks[index];\n        task.start();\n        try {\n          await task.result;\n        } catch (error) {\n          reportAfterError(state, error);\n        }\n      }\n\n      state.phase = \"closed\";\n    }),\n  );\n\n  return state;\n}\n\nfunction registerPlatformLifetime(\n  state: AfterRequestState,\n  context: FarmAfterPlatformContext | undefined,\n): void {\n  if (!context?.waitUntil) return;\n\n  try {\n    context.waitUntil(state.completion);\n  } catch (error) {\n    reportAfterError(state, error);\n  }\n}\n\nfunction registerResponseFinishedHook(\n  state: AfterRequestState,\n  context: FarmAfterPlatformContext | undefined,\n): boolean {\n  if (!context?.onResponseFinished) return false;\n\n  try {\n    context.onResponseFinished(state.finishResponse);\n    return true;\n  } catch (error) {\n    reportAfterError(state, error);\n    return false;\n  }\n}\n\nfunction finishSoon(state: AfterRequestState): void {\n  setTimeout(state.finishResponse, 0);\n}\n\nfunction wrapResponseBody(\n  response: Response,\n  request: Request,\n  state: AfterRequestState,\n): Response {\n  if (\n    request.method === \"HEAD\" ||\n    response.status < 200 ||\n    response.status > 599 ||\n    !response.body ||\n    response.bodyUsed ||\n    response.body.locked\n  ) {\n    finishSoon(state);\n    return response;\n  }\n\n  const reader = response.body.getReader();\n  let released = false;\n  const releaseReader = () => {\n    if (released) return;\n    released = true;\n    reader.releaseLock();\n  };\n\n  const body = new ReadableStream<Uint8Array>({\n    async pull(controller) {\n      try {\n        const chunk = await reader.read();\n        if (chunk.done) {\n          releaseReader();\n          controller.close();\n          finishSoon(state);\n          return;\n        }\n        controller.enqueue(chunk.value);\n      } catch (error) {\n        releaseReader();\n        controller.error(error);\n        finishSoon(state);\n      }\n    },\n    cancel(reason) {\n      try {\n        // Preserve the caller's cleanup promise, but response completion must\n        // not depend on whether producer-owned cancellation ever settles.\n        return reader.cancel(reason);\n      } finally {\n        releaseReader();\n        finishSoon(state);\n      }\n    },\n  });\n\n  return new Response(body, {\n    headers: response.headers,\n    status: response.status,\n    statusText: response.statusText,\n  });\n}\n\n/**\n * Schedule non-blocking work for after the current response finishes.\n *\n * Callbacks run in registration order. A callback failure is reported without\n * changing the response or preventing later callbacks from running.\n */\nexport function after(callback: AfterCallback): void {\n  if (typeof callback !== \"function\") {\n    throw new TypeError(\"after() expects a callback function.\");\n  }\n\n  const state = afterStorage.getStore();\n  if (!state) {\n    throw new Error(\"after() can only be used while Farm is handling a server request.\");\n  }\n  if (state.phase === \"closed\") {\n    throw new Error(\"after() cannot schedule work after the request lifecycle has completed.\");\n  }\n\n  let start!: () => void;\n  const ready = new Promise<void>((resolve) => {\n    start = resolve;\n  });\n\n  // Registering the continuation here preserves every request AsyncLocalStorage\n  // context that is active at the after() call site.\n  const result = ready.then(callback);\n  state.tasks.push({ result, start });\n}\n\n/** @internal Run a Web Request handler inside Farm's post-response lifecycle. */\nexport async function _runWithAfterRequest(\n  request: Request,\n  handler: () => Response | Promise<Response>,\n  context?: FarmAfterPlatformContext,\n): Promise<Response> {\n  if (afterStorage.getStore()) {\n    return await handler();\n  }\n\n  const state = createAfterRequestState();\n  registerPlatformLifetime(state, context);\n  const hasResponseHook = registerResponseFinishedHook(state, context);\n\n  try {\n    const response = await afterStorage.run(state, handler);\n    return hasResponseHook ? response : wrapResponseBody(response, request, state);\n  } catch (error) {\n    // The handler threw, so there is no successful response for a\n    // response-finished hook to fire on. Run the after-lifecycle now regardless\n    // of the hook; otherwise a spec-compliant adapter (whose hook only fires on\n    // a real response) never runs the registered after() callbacks and\n    // waitUntil(state.completion) hangs forever. finishResponse is idempotent,\n    // so a later hook firing (e.g. a Node error response's close) is a no-op.\n    finishSoon(state);\n    throw error;\n  }\n}\n\n/** @internal Run a Node response handler inside Farm's post-response lifecycle. */\nexport async function _runWithAfterNodeResponse<T>(\n  response: ServerResponse,\n  handler: () => T | Promise<T>,\n  context?: Pick<FarmAfterPlatformContext, \"waitUntil\">,\n): Promise<T> {\n  if (afterStorage.getStore()) {\n    return await handler();\n  }\n\n  const state = createAfterRequestState();\n  let finished = false;\n  const finish = () => {\n    if (finished) return;\n    finished = true;\n    response.off(\"finish\", finish);\n    response.off(\"close\", finish);\n    state.finishResponse();\n  };\n\n  response.once(\"finish\", finish);\n  response.once(\"close\", finish);\n  if (response.writableEnded) finishSoon(state);\n  registerPlatformLifetime(state, context);\n\n  return await afterStorage.run(state, handler);\n}\n\n/** @internal Add Farm's post-response lifecycle to a Node middleware. */\nexport function _withAfterNodeMiddleware(\n  handler: (\n    request: IncomingMessage,\n    response: ServerResponse,\n    next: (error?: unknown) => void,\n  ) => void | Promise<void>,\n): (\n  request: IncomingMessage,\n  response: ServerResponse,\n  next: (error?: unknown) => void,\n) => Promise<void> {\n  return async (request, response, next) => {\n    await _runWithAfterNodeResponse(response, () => handler(request, response, next));\n  };\n}\n"],"mappings":"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","names":[]}