@web3r/flowerkit
Version:
Tree-shakable JavaScript and TypeScript utility library for frontend/browser apps: DOM, events, arrays, objects, strings, date, JSON, and network helpers (ESM/CJS, SSR-friendly).
1 lines • 14.4 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":[""],"sourcesContent":["\r\nimport { getDocument, getWindow } from \"ssr-window\";\nimport { bubble } from \"../../evt/bubble/index.ts\";\nimport { getObjFromFormData } from \"../../obj/getObjFromFormData/index.ts\";\nimport { getFormDataFromObj } from \"../getFormDataFromObj/index.ts\";\nimport { getUrlWithQueryParams } from \"../getUrlWithQueryParams/index.ts\";\n\nexport type TGetFromServerArgs<TResp = unknown, TSuccess = TResp> = {\n contentType?: \"auto\" | \"application/json\" | \"application/x-www-form-urlencoded\" | \"multipart/form-data\";\n isBubble?: boolean;\n timeout?: number;\n method?: \"GET\" | \"PUT\" | \"POST\" | \"DELETE\" | \"HEAD\" | \"CONNECT\" | \"OPTIONS\" | \"TRACE\" | \"PATCH\";\n mode?: RequestMode;\n signal?: AbortSignal | null;\n data?: Record<string, unknown> | FormData | null;\n getSuccessResp?: (data: TResp) => TSuccess;\n getResp?: (resp: Response) => Promise<TResp>;\n type?: \"text\" | \"json\" | \"blob\" | \"arrayBuffer\";\n url?: string;\n headers?: Record<string, string>;\n allowedCodes?: number[];\n credentials?: RequestCredentials;\n redirect?: RequestRedirect;\r\n cache?: RequestCache;\r\n referrerPolicy?: ReferrerPolicy;\r\n fetchProps?: Omit<RequestInit, \"method\" | \"headers\" | \"body\" | \"signal\" | \"mode\" | \"credentials\" | \"redirect\" | \"cache\" | \"referrerPolicy\">;\r\n};\r\n\r\nexport type TGetFromServerReturn = ReturnType<typeof getFromServer>;\r\n\r\n/**\r\n * Performs an HTTP request (`fetch`) with handy defaults, content-type handling,\r\n * query param building, and optional bubbling of a \"getFromServer\" event.\r\n *\r\n * @template T The expected response data type.\r\n * @param {Object} [props] Request parameters (all optional).\r\n * @param {string} [props.url] The URL to request. Defaults to current window location or './'.\r\n * @param {(\"auto\"|\"application/json\"|\"application/x-www-form-urlencoded\"|\"multipart/form-data\")} [props.contentType=\"auto\"] Content type header. If \"auto\", sets based on data/method.\r\n * @param {boolean} [props.isBubble=true] Whether to bubble a \"getFromServer\" event after success.\r\n * @param {number} [props.timeout=15000] Timeout in milliseconds (use Infinity to disable).\r\n * @param {(\"GET\"|\"PUT\"|\"POST\"|\"DELETE\"|\"HEAD\"|\"CONNECT\"|\"OPTIONS\"|\"TRACE\")} [props.method=\"GET\"] HTTP method.\r\n * @param {RequestMode} [props.mode=\"cors\"] Fetch mode.\r\n * @param {AbortSignal|null} [props.signal=null] AbortSignal for cancellation.\r\n * @param {Record<string,unknown>|FormData|null} [props.data=null] Request data. For GET-like methods, appended as query params.\r\n * @param {function(T): T} [props.getSuccessResp] Transform function for successful response. Defaults to identity function.\r\n * @param {function(Response): Promise<T>} [props.getResp] Custom response parser. If provided, overrides `type`.\r\n * @param {(\"text\"|\"json\"|\"blob\"|\"arrayBuffer\")} [props.type=\"json\"] Response body parsing type (used when `getResp` not provided).\r\n * @param {Record<string,string>} [props.headers={}] Additional headers.\r\n * @param {number[]} [props.allowedCodes=[]] Array of HTTP status codes to treat as success even if not 2xx.\r\n * @param {RequestCredentials} [props.credentials=\"same-origin\"] Credentials mode.\r\n * @param {RequestRedirect} [props.redirect=\"follow\"] Redirect mode.\r\n * @param {RequestCache} [props.cache=\"default\"] Cache mode.\r\n * @param {ReferrerPolicy} [props.referrerPolicy=\"no-referrer-when-downgrade\"] Referrer policy.\r\n * @param {Omit<RequestInit, \"method\"|\"headers\"|\"body\"|\"signal\"|\"mode\"|\"credentials\"|\"redirect\"|\"cache\"|\"referrerPolicy\">} [props.fetchProps={}] Additional fetch options.\r\n * @returns {Promise<T>} Promise with parsed response (type depends on `type` option).\r\n * @throws {TypeError} getFromServer: url must be a string\n * @throws {TypeError} getFromServer: allowedCodes must be an array of integers\n * @throws {TypeError} getFromServer: data must be a plain object, FormData, or null\n * @throws {TypeError} getFromServer: timeout must be a non-negative number or Infinity\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\n * @example\n * const user = await getFromServer<{ userId: number }>({ url: \"/api/user?id=1\", method: \"GET\" });\n */\nexport const getFromServer = async <TResp = unknown, TSuccess = TResp>(\n props: TGetFromServerArgs<TResp, TSuccess> = {}\n): Promise<TSuccess> => {\n const {\n contentType = \"auto\",\n isBubble = true,\n timeout = 15000,\n method = \"GET\",\n mode = \"cors\",\n signal = null,\n data = null,\n getResp,\n type = \"json\",\n url = getWindow().location.href || \"./\",\n headers = {},\n allowedCodes = [],\n credentials = \"same-origin\",\r\n redirect = \"follow\",\r\n referrerPolicy = \"no-referrer-when-downgrade\",\r\n cache = \"default\",\n fetchProps = {},\n } = props;\n\n const getSuccessResp = props.getSuccessResp ?? ((resp: TResp) => resp as unknown as TSuccess);\n const methodNormalized = String(method).toUpperCase() as Uppercase<typeof method>;\n const methodsWithBody = new Set([\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"PATCH\",\n ]);\n const methodsNoBody = new Set([\n \"GET\",\n \"HEAD\",\n \"CONNECT\",\n \"OPTIONS\",\n \"TRACE\",\n ]);\n const isFormData = (v: unknown): v is FormData => typeof FormData !== \"undefined\" && v instanceof FormData;\n const isPlainObject = (v: unknown): v is Record<string, unknown> => {\n return Object.prototype.toString.call(v) === \"[object Object]\";\n };\n\n if (typeof url !== \"string\") {\n throw new TypeError(\"getFromServer: url must be a string\");\n }\n\n if (!Array.isArray(allowedCodes) || !allowedCodes.every((c) => Number.isInteger(c))) {\n throw new TypeError(\"getFromServer: allowedCodes must be an array of integers\");\n }\n\n if (typeof timeout !== \"number\" || (Number.isFinite(timeout) && timeout < 0) || Number.isNaN(timeout)) {\n throw new TypeError(\"getFromServer: timeout must be a non-negative number or Infinity\");\n }\n\n if (data !== null && !isFormData(data) && !isPlainObject(data)) {\n throw new TypeError(\"getFromServer: data must be a plain object, FormData, or null\");\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n let isTimedOut = false;\n const requestController = new AbortController();\n const externalAbortListener = (): void => {\n requestController.abort();\n };\n if (signal) {\n if (signal.aborted) {\n externalAbortListener();\n } else {\n signal.addEventListener(\"abort\", externalAbortListener, { once: true });\n }\n }\n\n const getDataAsObject = (): Record<string, unknown> => {\n return isFormData(data) ? getObjFromFormData(data) : (data ?? {});\n };\n\n /**\n * Produces request body based on contentType and data\n * @private\n * @returns {BodyInit | null}\n */\n const getBody = (): BodyInit | null => {\n if (!methodsWithBody.has(methodNormalized)) {\n return null;\n }\n\n switch (true) {\n case contentType === \"application/json\":\n return JSON.stringify(getDataAsObject());\n case contentType === \"application/x-www-form-urlencoded\": {\n const params = new URLSearchParams();\n Object.entries(getDataAsObject())\n .forEach(([ key, value ]) => {\n params.set(key, String(value ?? \"\"));\n });\n return params.toString();\n }\n case contentType === \"multipart/form-data\":\n return isFormData(data)\n ? data\n : getFormDataFromObj(getDataAsObject());\n case contentType === \"auto\":\n return isFormData(data)\n ? data\n : getFormDataFromObj(getDataAsObject());\n default:\n return null;\n }\n };\n\n /**\r\n * URL builder (adds query params for GET-like methods)\n * @private\n */\n const getUrl = (): string => {\n return (methodsNoBody.has(methodNormalized) && data !== null)\n ? getUrlWithQueryParams(\n url, isFormData(data)\n ? data\n : (data as Record<string, string | number | boolean | null>)\n )\n : url;\r\n };\r\n\r\n /**\r\n * Response parser\n * @private\n */\n const getResponse = async (resp: Response): Promise<TResp> => {\n if (typeof getResp === \"function\") {\n return await getResp(resp);\n }\n const { ok, status } = resp;\n if (ok || (allowedCodes.length > 0 && allowedCodes.includes(status))) {\n switch (type) {\n case \"arrayBuffer\": return await resp.arrayBuffer() as unknown as TResp;\n case \"json\": return await resp.json() as TResp;\n case \"blob\": return await resp.blob() as unknown as TResp;\n default: return await resp.text() as unknown as TResp;\n }\n }\n throw resp;\n };\n\n /**\n * Headers builder\n * @private\n */\r\n const getHeaders = (): Record<string, string> => {\n const result: Record<string, string> = { ...(headers || {}) };\n if (contentType === \"multipart/form-data\") {\n Object.keys(result).forEach((key) => {\n if (key.toLowerCase() === \"content-type\") {\n delete result[key];\n }\n });\n return result;\n }\n if ([ \"application/json\", \"application/x-www-form-urlencoded\" ].includes(contentType)) {\n result[\"Content-Type\"] = contentType;\n }\n return result;\n };\n\n if (timeout && timeout !== Infinity) {\n timer = setTimeout(() => {\n isTimedOut = true;\n requestController.abort();\n }, timeout);\n }\n\n const fetchParams: RequestInit = {\n ...fetchProps,\n method: methodNormalized,\n body: getBody(),\n mode,\n signal: requestController.signal,\n credentials,\n redirect,\n cache,\n referrerPolicy,\n headers: getHeaders(),\n };\n\n try {\n const resp = await fetch(getUrl(), fetchParams);\n const parsed = await getResponse(resp);\n\n if (isBubble && typeof window !== \"undefined\") {\n bubble(getDocument(), getFromServer.name, parsed);\n }\n\n return getSuccessResp(parsed);\n } catch (error) {\n if (isTimedOut) {\n throw 408;\n }\n throw error;\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n if (signal) {\n signal.removeEventListener(\"abort\", externalAbortListener);\n }\n }\n};\n"],"names":["getFromServer","async","props","contentType","isBubble","timeout","method","mode","signal","data","getResp","type","url","getWindow","location","href","headers","allowedCodes","credentials","redirect","referrerPolicy","cache","fetchProps","getSuccessResp","resp","methodNormalized","String","toUpperCase","methodsWithBody","Set","methodsNoBody","isFormData","v","FormData","isPlainObject","Object","prototype","toString","call","TypeError","Array","isArray","every","c","Number","isInteger","isFinite","isNaN","timer","isTimedOut","requestController","AbortController","externalAbortListener","abort","aborted","addEventListener","once","getDataAsObject","getObjFromFormData","getBody","has","JSON","stringify","params","URLSearchParams","entries","forEach","key","value","set","getFormDataFromObj","getUrl","getUrlWithQueryParams","getResponse","ok","status","length","includes","arrayBuffer","json","blob","text","getHeaders","result","keys","toLowerCase","Infinity","setTimeout","fetchParams","body","fetch","parsed","window","bubble","getDocument","name","error","clearTimeout","removeEventListener"],"mappings":"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"}