{"version":3,"file":"FetcherBase.mjs","names":[],"sources":["../../src/internal/FetcherBase.ts"],"sourcesContent":["import { HttpError } from \"../HttpError\";\nimport { IConnection } from \"../IConnection\";\nimport { IFetchEvent } from \"../IFetchEvent\";\nimport { IFetchRoute } from \"../IFetchRoute\";\nimport { IPropagation } from \"../IPropagation\";\nimport { is_binary_response_content_type } from \"./is_binary_response_content_type\";\nimport { join_host_and_path, normalize_route_path } from \"./join_host_and_path\";\n\n/** @internal */\nexport namespace FetcherBase {\n  export interface IProps {\n    className: string;\n    encode: (\n      input: any,\n      headers: Record<string, IConnection.HeaderValue | undefined>,\n    ) => string;\n    decode: (\n      input: string,\n      headers: Record<string, IConnection.HeaderValue | undefined>,\n    ) => any;\n  }\n\n  export const request =\n    (props: IProps) =>\n    async <Input, Output>(\n      connection: IConnection,\n      route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n      input?: Input,\n      stringify?: (input: Input) => string,\n    ): Promise<Output> => {\n      const result = await _Propagate(\"fetch\")(props)(\n        connection,\n        route,\n        input,\n        stringify,\n      );\n      if ((result as any).success === false)\n        throw new HttpError(\n          route.method,\n          route.path,\n          result.status as any as number,\n          result.headers,\n          result.data as string,\n        );\n      return result.data as Output;\n    };\n\n  export const propagate =\n    (props: IProps) =>\n    async <Input>(\n      connection: IConnection,\n      route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n      input?: Input,\n      stringify?: (input: Input) => string,\n    ): Promise<IPropagation<any, any>> =>\n      _Propagate(\"propagate\")(props)(connection, route, input, stringify);\n\n  /** @internal */\n  const _Propagate =\n    (method: string) =>\n    (props: IProps) =>\n    async <Input>(\n      connection: IConnection,\n      route: IFetchRoute<\"DELETE\" | \"GET\" | \"HEAD\" | \"PATCH\" | \"POST\" | \"PUT\">,\n      input?: Input,\n      stringify?: (input: Input) => string,\n    ): Promise<IPropagation<any, any>> => {\n      //----\n      // REQUEST MESSAGE\n      //----\n      // METHOD & HEADERS\n      const headers: Record<string, IConnection.HeaderValue | undefined> = {\n        ...(connection.headers ?? {}),\n      };\n      if (input !== undefined) {\n        if (route.request?.type === undefined)\n          throw new Error(\n            `Error on ${props.className}.fetch(): no content-type being configured.`,\n          );\n        else if (route.request.type !== \"multipart/form-data\") {\n          deleteHeader(headers, \"content-type\");\n          headers[\"Content-Type\"] = route.request.type;\n        }\n      } else if (input === undefined) deleteHeader(headers, \"content-type\");\n\n      // INIT REQUEST DATA\n      const init: RequestInit = {\n        ...(connection.options ?? {}),\n        method: route.method,\n        headers: (() => {\n          const output: [string, string][] = [];\n          for (const [key, value] of Object.entries(headers))\n            if (value === undefined) continue;\n            else if (Array.isArray(value))\n              for (const v of value) output.push([key, String(v)]);\n            else output.push([key, String(value)]);\n          return output;\n        })(),\n      };\n\n      // CONSTRUCT BODY DATA\n      if (input !== undefined)\n        init.body = props.encode(\n          // BODY TRANSFORM\n          route.request?.type === \"application/x-www-form-urlencoded\"\n            ? request_query_body(input)\n            : route.request?.type === \"multipart/form-data\"\n              ? request_form_data_body(input as any)\n              : route.request?.type !== \"text/plain\"\n                ? (stringify ?? JSON.stringify)(input)\n                : input,\n          headers,\n        );\n\n      //----\n      // RESPONSE MESSAGE\n      //----\n      // URL SPECIFICATION\n      const path: string = normalize_route_path(route.path);\n      const url: URL = new URL(join_host_and_path(connection.host, route.path));\n\n      // DO FETCH\n      const event: IFetchEvent = {\n        route,\n        path,\n        status: null,\n        input,\n        output: undefined,\n        started_at: new Date(),\n        respond_at: null,\n        completed_at: null!,\n      };\n      try {\n        // TRY FETCH\n        const response: Response = await (connection.fetch ?? fetch)(\n          url.href,\n          init,\n        );\n        event.respond_at = new Date();\n        event.status = response.status;\n\n        // CONSTRUCT RESULT DATA\n        const result: IPropagation<any, any> = {\n          success:\n            response.status === 200 ||\n            response.status === 201 ||\n            response.status === route.status,\n          status: response.status,\n          headers: response_headers_to_object(response.headers),\n          data: undefined!,\n        } as any;\n        if ((result as any).success === false) {\n          // WHEN FAILED\n          result.data = await response.text();\n          const type = response.headers.get(\"content-type\");\n          if (\n            method !== \"fetch\" &&\n            type &&\n            type.indexOf(\"application/json\") !== -1\n          )\n            try {\n              result.data = JSON.parse(result.data);\n            } catch {}\n        } else {\n          // WHEN SUCCESS\n          if (route.method === \"HEAD\") result.data = undefined!;\n          else if (route.response?.type === \"application/json\") {\n            const text: string = await response.text();\n            result.data = text.length ? JSON.parse(text) : undefined;\n          } else if (\n            route.response?.type === \"application/x-www-form-urlencoded\"\n          ) {\n            const query: URLSearchParams = new URLSearchParams(\n              await response.text(),\n            );\n            result.data = route.parseQuery ? route.parseQuery(query) : query;\n          } else if (is_binary_response_content_type(route.response?.type))\n            result.data = response.body ?? new ReadableStream<Uint8Array>();\n          else\n            result.data = props.decode(await response.text(), result.headers);\n        }\n        event.output = result.data;\n        return result;\n      } catch (exp) {\n        throw exp;\n      } finally {\n        event.completed_at = new Date();\n        if (connection.logger)\n          try {\n            await connection.logger(event);\n          } catch {}\n      }\n    };\n}\n\n/** @internal */\nconst request_query_body = (input: any): URLSearchParams => {\n  const q: URLSearchParams = new URLSearchParams();\n  for (const [key, value] of Object.entries(input))\n    if (value === undefined) continue;\n    else if (Array.isArray(value))\n      value.forEach((elem) => q.append(key, String(elem)));\n    else q.set(key, String(value));\n  return q;\n};\n\n/** @internal */\nconst request_form_data_body = (input: Record<string, any>): FormData => {\n  const encoded: FormData = new FormData();\n  const append = (key: string) => (value: any) => {\n    if (value === undefined) return;\n    else if (typeof File === \"function\" && value instanceof File)\n      encoded.append(key, value, value.name);\n    else encoded.append(key, value);\n  };\n  for (const [key, value] of Object.entries(input))\n    if (Array.isArray(value)) value.map(append(key));\n    else append(key)(value);\n  return encoded;\n};\n\n/** @internal */\nconst response_headers_to_object = (\n  headers: Headers,\n): Record<string, string | string[]> => {\n  const output: Record<string, string | string[]> = {};\n  const cookieHeaders: string[] | undefined =\n    typeof (headers as any).getSetCookie === \"function\"\n      ? (headers as any).getSetCookie()\n      : undefined;\n  if (cookieHeaders?.length) output[\"set-cookie\"] = cookieHeaders;\n\n  headers.forEach((value, key) => {\n    if (key.toLowerCase() === \"set-cookie\") {\n      if (cookieHeaders?.length) return;\n      output[key] ??= [];\n      (output[key] as string[]).push(value);\n    } else output[key] = value;\n  });\n  return output;\n};\n\nconst deleteHeader = (\n  headers: Record<string, IConnection.HeaderValue | undefined>,\n  name: string,\n): void => {\n  const normalized: string = name.toLowerCase();\n  for (const key of Object.keys(headers))\n    if (key.toLowerCase() === normalized) delete headers[key];\n};\n"],"mappings":"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"}