{"version":3,"file":"action.cjs","names":["#fn","ActionError","getExpectedRequestStore"],"sources":["../../src/extra/utils/ip.ts","../../src/extra/action.ts"],"sourcesContent":["const IPV4_REGEX =\n  /^(?:(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$/;\nconst IPV6_REGEX =\n  /^((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!3)::|:\\b|$))|(?!23)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})$/i;\n\n/** @internal */\nexport function isIP(ip: string | null) {\n  return ip !== null && (IPV4_REGEX.test(ip) || IPV6_REGEX.test(ip));\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { SafeReturn } from \"p-safe\";\n\nimport { headers } from \"next/headers\";\nimport { ResponseCookies, RequestCookies } from \"@edge-runtime/cookies\";\nimport {\n  hasOwnProp,\n  isNil,\n  isPlainObject,\n  isUndefined\n} from \"@rzl-zone/utils-js/predicates\";\nimport { assertIsString } from \"@rzl-zone/utils-js/assertions\";\n\nimport { isIP } from \"@/extra/utils/ip\";\nimport { getExpectedRequestStore } from \"@/extra/utils/async-storages\";\nimport { ActionError, type ActionErrorJson } from \"@/extra/utils/errors\";\n\n// -- Types ---------------------------\n\ntype AnyFn<This = void> = (this: This, ...args: readonly any[]) => unknown;\n\ntype ActionFunc<T> = T extends (...args: infer Args) => infer Return\n  ? (\n      this: any,\n      ...args: Args\n    ) => Promise<SafeReturn<Awaited<Return>, ActionError>>\n  : never;\n\ninterface ActionContext<Return> {\n  resolve: (result: Return) => never;\n  reject: (error: ActionErrorJson | ActionError) => never;\n}\n\n// -- Internal ------------------------\n\nclass ActionClass<Return extends AnyFn<ActionContext<any>>> {\n  #fn: AnyFn<ActionContext<any>> | undefined;\n  private fn: AnyFn<ActionContext<any>> | undefined;\n\n  constructor(fn: AnyFn<ActionContext<any>>) {\n    if (typeof globalThis?.Reflect?.ownKeys === \"function\") {\n      // ES2022+ environment\n      this.#fn = fn;\n    } else {\n      this.fn = fn;\n    }\n  }\n\n  resolve(result: Return): never {\n    throw { data: result };\n  }\n\n  reject(reason: ActionErrorJson | ActionError): never {\n    throw reason;\n  }\n\n  /** @internal */\n  async run(...args: any[]) {\n    try {\n      const fnToCall = this.#fn ?? this.fn;\n      const result_ = await fnToCall?.apply(this, args);\n      return !isUndefined(result_) ? { data: result_ } : { data: void 0 };\n    } catch (e: unknown) {\n      if (!!e && isPlainObject(e) && hasOwnProp(e, \"data\"))\n        return e as {\n          data: unknown;\n        };\n      if (e instanceof ActionError) return { error: e.toJSON() };\n      if (\n        !!e &&\n        isPlainObject(e) &&\n        hasOwnProp(e, \"code\") &&\n        hasOwnProp(e, \"message\")\n      ) {\n        return {\n          error: {\n            code: e.code,\n            message: e.message,\n            stack: e[\"stack\"]\n          }\n        };\n      }\n      throw e;\n    }\n  }\n}\n\n/** Parses the `x-forwarded-for` header to extract the client's IP address.\n *\n * This header may contain multiple IP addresses in the format \"client IP, proxy 1 IP, proxy 2 IP\".\n * This function extracts and returns the first valid IP address.\n *\n * @link https://github.com/pbojinov/request-ip/blob/e1d0f4b89edf26c77cf62b5ef662ba1a0bd1c9fd/src/index.js#L9\n *\n * @internal\n */\nfunction getClientIpFromXForwardedFor(value: null | undefined | string) {\n  if (isNil(value)) return null;\n\n  assertIsString(value);\n\n  // x-forwarded-for may return multiple IP addresses in the format:\n  // \"client IP, proxy 1 IP, proxy 2 IP\"\n  // Therefore, the right-most IP address is the IP address of the most recent proxy\n  // and the left-most IP address is the IP address of the originating client.\n  // source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For\n  // Azure Web App's also adds a port for some reason, so we'll only use the first part (the IP)\n  const forwardedIps = value.split(\",\").map((e) => {\n    const ip = e.trim();\n    if (ip.includes(\":\")) {\n      const splitted = ip.split(\":\");\n      // make sure we only use this if it's ipv4 (ip:port)\n      if (splitted.length === 2) {\n        return splitted[0];\n      }\n    }\n    return ip;\n  });\n\n  // Sometimes IP addresses in this header can be 'unknown' (http://stackoverflow.com/a/11285650).\n  // Therefore, taking the right-most IP address that is not unknown\n  // A Squid configuration directive can also set the value to \"unknown\" (http://www.squid-cache.org/Doc/config/forwarded_for/)\n  for (let i = 0; i < forwardedIps.length; i++) {\n    const ip = forwardedIps[i];\n    if (ip && isIP(ip)) {\n      return ip;\n    }\n  }\n\n  // If no value in the split list is an ip, return null\n  return null;\n}\n\n// -- Exported ------------------------\n\nexport type Action<Return extends AnyFn<ActionContext<any>>> = InstanceType<\n  typeof ActionClass<Return>\n>;\n\n/** -------------------------------------------------------------------\n * * ***A helper to simplify creating Next.js Server Actions.***\n * -------------------------------------------------------------------\n * * ***`⚠️ Warning: Currently is not support with turbopack flag mode !!!`***\n * -------------------------------------------------------------------\n * @example\n * * ***`actions.ts:`***\n * ```tsx\n * \"use server\";\n *\n * import { actionError, createAction } from \"@rzl-zone/next-kit/extra/action\";\n *\n * export const hello = createAction(async (name: string) => {\n *    if (!name) {\n *      actionError(\"NAME_REQUIRED\", \"Name is required\");\n *    }\n *    return `Hello, ${name}!`;\n * });\n * ```\n * ---\n * * ***`page.tsx (server-component):`***\n * ```tsx\n * import { hello } from \"./actions\";\n *\n * export default async function Page() {\n *    const { data, error } = await hello(\"John\");\n *    if (error) return <h1>ERROR: {error.message}</h1>;\n *    return <h1>{data}</h1>;\n * }\n * ```\n */\nexport function createAction<T extends AnyFn<ActionContext<any>>>(\n  fn: T\n): ActionFunc<T> {\n  const action = new ActionClass<T>(fn);\n\n  return new Proxy(fn as any, {\n    apply: (target, thisArg, argumentsList) => {\n      return action.run(...argumentsList);\n    }\n  });\n}\n\n/** -------------------------------------------------------------------\n * * ***This function is for throw error from `createAction`.***\n * -------------------------------------------------------------------\n * * ***`⚠️ Warning: Currently is not support with turbopack flag mode !!!`***\n * -------------------------------------------------------------------\n * @example\n * * ***`actions.ts:`***\n * ```ts\n * \"use server\";\n *\n * import { actionError, createAction } from \"@rzl-zone/next-kit/extra/action\";\n *\n * export const hello = createAction(async (name: string) => {\n *    if (!name) {\n *      actionError(\"NAME_REQUIRED\", \"Name is required\");\n *    }\n *    return `Hello, ${name}!`;\n * });\n * ```\n * ---\n * * ***`page.tsx (server-component):`***\n * ```tsx\n * import { hello } from \"./actions\";\n *\n * export default async function Page() {\n *    const { data, error } = await hello(\"John\");\n *    if (error) return <h1>ERROR: {error.message}</h1>;\n *    return <h1>{data}</h1>;\n * }\n * ```\n */\nexport function actionError(code: string, message: string): never {\n  const e = new ActionError(code, message);\n  Error.captureStackTrace(e, actionError);\n  throw e;\n}\n\n/** -------------------------------------------------------------------\n * * ***Handles HTTP cookies for server-side components and actions.***\n * -------------------------------------------------------------------\n * * ***`⚠️ Warning: Currently is not support with turbopack flag mode !!!`***\n * -------------------------------------------------------------------\n * **This function leverages a shared request storage mechanism to access or modify the cookies.**\n * - ***This function serves two primary purposes:***\n *    1. Reading cookies from an incoming HTTP request when used in a Server Component.\n *    2. Writing cookies to an outgoing HTTP response when used in a Server Component, Server Action, or Route Handler.\n *\n * @returns An object representing the mutable cookies for the current HTTP request context.\n *\n * @throws {Error} Throws an error if the request storage is not available, indicating an internal consistency issue.\n *\n * @example\n * // Reading cookies in a Server Component\n * import { cookies } from \"@rzl-zone/next-kit/extra/action\";\n *\n * export default function Page() {\n *   const requestCookies = cookies();\n *   console.log(requestCookies.get(\"sessionId\"));\n *\n *   return (\n *     //...\n *   );\n * }\n *\n * @example\n * // Writing cookies in a Server Action\n * import { cookies } from \"@rzl-zone/next-kit/extra/action\";\n *\n * export function myAction() {\n *   const responseCookies = cookies();\n *   responseCookies.set(\"sessionId\", \"abc123\", { httpOnly: true });\n * };\n *\n * @example\n * // Modifying cookies in a Route Handler\n * import { cookies } from \"@rzl-zone/next-kit/extra/action\";\n *\n * export default async function handler(req, res) {\n *   const responseCookies = cookies();\n *   responseCookies.delete(\"sessionId\");\n *   res.end(\"Cookie deleted\");\n * };\n */\nexport function cookies(): ResponseCookies {\n  const expression = \"cookies\";\n  const store = getExpectedRequestStore(expression);\n\n  return store.mutableCookies;\n}\n\n/** -------------------------------------------------------------------\n * * ***Retrieves the client's IP address from request headers.***\n * -------------------------------------------------------------------\n * * ***`⚠️ Warning: Currently is not support with turbopack flag mode !!!`***\n * -------------------------------------------------------------------\n * ***The function checks various headers commonly used by different cloud providers and proxies to find the client's IP address.***\n * ***It prioritizes the `x-forwarded-for` header, which may contain multiple IP addresses, and extracts the first one.***\n *\n * ***If no valid IP is found, it return null.***\n *\n * @returns The client's IP address.\n */\nexport async function clientIP(): Promise<string | null> {\n  const hs = await headers();\n\n  // X-Forwarded-For (Header may return multiple IP addresses in the format: \"client IP, proxy 1 IP, proxy 2 IP\", so we take the first one.)\n  if (hs.has(\"x-forwarded-for\")) {\n    const forwardedIp = getClientIpFromXForwardedFor(hs.get(\"x-forwarded-for\"));\n    if (forwardedIp) {\n      return forwardedIp;\n    }\n  }\n\n  const headerKeys = [\n    // Standard headers used by Amazon EC2, Heroku, and others.\n    \"x-client-ip\",\n\n    // Cloudflare.\n    // @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-\n    // CF-Connecting-IP - applied to every request to the origin.\n    \"cf-connecting-ip\",\n\n    // Fastly and Firebase hosting header (When forwarded to cloud function)\n    \"fastly-client-ip\",\n\n    // Akamai and Cloudflare: True-Client-IP.\n    \"true-client-ip\",\n\n    // X-Real-IP (Nginx proxy/FastCGI)\n    \"x-real-ip\",\n\n    // X-Cluster-Client-IP (Rackspace LB, Riverbed Stingray)\n    \"x-cluster-client-ip\",\n\n    // X-Forwarded, Forwarded-For and Forwarded (Variations of #2)\n    \"x-forwarded\",\n    \"forwarded-for\",\n    \"forwarded\",\n\n    // Google Cloud App Engine\n    // https://cloud.google.com/appengine/docs/standard/go/reference/request-response-headers\n    \"x-appengine-user-ip\",\n\n    // Cloudflare fallback\n    // https://blog.cloudflare.com/eliminating-the-last-reasons-to-not-enable-ipv6/#introducingpseudoipv4\n    \"Cf-Pseudo-IPv4\"\n  ];\n\n  for (const headerKey of headerKeys) {\n    if (hs.has(headerKey)) {\n      const ip = hs.get(headerKey);\n      if (ip && isIP(ip)) {\n        return ip;\n      }\n    }\n  }\n\n  return null;\n}\n\n// -- Third ---------------------------\n\nexport { ResponseCookies, RequestCookies };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,MAAM,aACJ;AACF,MAAM,aACJ;;AAGF,SAAgB,KAAK,IAAmB;CACtC,OAAO,OAAO,SAAS,WAAW,KAAK,EAAE,KAAK,WAAW,KAAK,EAAE;AAClE;;;;AC2BA,IAAM,cAAN,MAA4D;CAC1D;CACA,AAAQ;CAER,YAAY,IAA+B;EACzC,IAAI,OAAO,YAAY,SAAS,YAAY,YAE1C,KAAKA,MAAM;OAEX,KAAK,KAAK;CAEd;CAEA,QAAQ,QAAuB;EAC7B,MAAM,EAAE,MAAM,OAAO;CACvB;CAEA,OAAO,QAA8C;EACnD,MAAM;CACR;;CAGA,MAAM,IAAI,GAAG,MAAa;EACxB,IAAI;GAEF,MAAM,UAAU,OADC,KAAKA,OAAO,KAAK,GACJ,EAAE,MAAM,MAAM,IAAI;GAChD,OAAO,gDAAa,OAAO,IAAI,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,KAAK,EAAE;EACpE,SAAS,GAAY;GACnB,IAAI,CAAC,CAAC,sDAAmB,CAAC,mDAAgB,GAAG,MAAM,GACjD,OAAO;GAGT,IAAI,aAAaC,4BAAa,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;GACzD,IACE,CAAC,CAAC,sDACY,CAAC,mDACJ,GAAG,MAAM,mDACT,GAAG,SAAS,GAEvB,OAAO,EACL,OAAO;IACL,MAAM,EAAE;IACR,SAAS,EAAE;IACX,OAAO,EAAE;GACX,EACF;GAEF,MAAM;EACR;CACF;AACF;;;;;;;;;;AAWA,SAAS,6BAA6B,OAAkC;CACtE,6CAAU,KAAK,GAAG,OAAO;CAEzB,kDAAe,KAAK;CAQpB,MAAM,eAAe,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM;EAC/C,MAAM,KAAK,EAAE,KAAK;EAClB,IAAI,GAAG,SAAS,GAAG,GAAG;GACpB,MAAM,WAAW,GAAG,MAAM,GAAG;GAE7B,IAAI,SAAS,WAAW,GACtB,OAAO,SAAS;EAEpB;EACA,OAAO;CACT,CAAC;CAKD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,KAAK,aAAa;EACxB,IAAI,MAAM,KAAK,EAAE,GACf,OAAO;CAEX;CAGA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,aACd,IACe;CACf,MAAM,SAAS,IAAI,YAAe,EAAE;CAEpC,OAAO,IAAI,MAAM,IAAW,EAC1B,QAAQ,QAAQ,SAAS,kBAAkB;EACzC,OAAO,OAAO,IAAI,GAAG,aAAa;CACpC,EACF,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,YAAY,MAAc,SAAwB;CAChE,MAAM,IAAI,IAAIA,2BAAY,MAAM,OAAO;CACvC,MAAM,kBAAkB,GAAG,WAAW;CACtC,MAAM;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,UAA2B;CAIzC,OAFcC,+CAAwB,SAE3B,CAAC,CAAC;AACf;;;;;;;;;;;;;AAcA,eAAsB,WAAmC;CACvD,MAAM,KAAK,mCAAc;CAGzB,IAAI,GAAG,IAAI,iBAAiB,GAAG;EAC7B,MAAM,cAAc,6BAA6B,GAAG,IAAI,iBAAiB,CAAC;EAC1E,IAAI,aACF,OAAO;CAEX;CAqCA,KAAK,MAAM,aAAa;EAjCtB;EAKA;EAGA;EAGA;EAGA;EAGA;EAGA;EACA;EACA;EAIA;EAIA;CAG+B,GAC/B,IAAI,GAAG,IAAI,SAAS,GAAG;EACrB,MAAM,KAAK,GAAG,IAAI,SAAS;EAC3B,IAAI,MAAM,KAAK,EAAE,GACf,OAAO;CAEX;CAGF,OAAO;AACT"}
