UNPKG

simple-typed-fetch

Version:
1 lines 9.73 kB
{"version":3,"sources":["../src/index.ts","../src/fetchWithValidation.ts","../src/simpleFetch.ts"],"sourcesContent":["import \"warp-isomorphic\";\n\nexport { default as fetchWithValidation } from \"./fetchWithValidation.js\";\nexport {\n default as simpleFetch,\n FetchWithValidationType,\n} from \"./simpleFetch.js\";\n","import type { Schema, z } from \"zod\";\n\nimport { err, fromPromise, fromThrowable, ok } from \"neverthrow\";\n\nexport default async function fetchWithValidation<\n DataOut,\n DataIn,\n ErrorOut,\n ErrorIn\n>(\n url: string,\n schema: Schema<DataOut, z.ZodTypeDef, DataIn>,\n options?: RequestInit,\n errorSchema?: Schema<ErrorOut, z.ZodTypeDef, ErrorIn>\n) {\n // Cases:\n // fetchError (no network, connection refused, connection break)\n // unknownFetchThrow\n // unknownGetTextError\n // unknownGetTextUnknownError\n // serverError\n // jsonParseError\n // jsonParseUnknownError\n // clientErrorWithResponsePayload\n // clientErrorPayloadParseError\n // clientError\n // payloadParseError\n // payload]\n\n const requestOptions = {\n ...options,\n headers: options?.headers ?? {},\n };\n\n const sharedData = {\n requestOptions,\n url,\n };\n const fetchResult = await fromPromise(fetch(url, requestOptions), (e) => {\n if (e instanceof Error) {\n return err({\n type: \"fetchError\" as const,\n ...sharedData,\n message: `Fetch error: ${e.message}. URL: ${url}`,\n error: e,\n });\n }\n\n return err({\n type: \"unknownFetchThrow\" as const,\n ...sharedData,\n message: `Unknown fetch error. URL: ${url}`,\n error: e,\n });\n });\n\n if (fetchResult.isErr()) {\n return fetchResult.error;\n }\n\n const response = fetchResult.value;\n const sharedDataWithResponse = {\n ...sharedData,\n response,\n };\n\n const textResult = await fromPromise(response.text(), (e) => {\n if (e instanceof Error) {\n return err({\n type: \"unknownGetTextError\" as const,\n ...sharedDataWithResponse,\n message: `Can't get response content: ${e.message}. URL: ${url}. HTTP ${response.status}`,\n error: e,\n });\n }\n\n return err({\n type: \"unknownGetTextUnknownError\" as const,\n ...sharedDataWithResponse,\n message: `Can't get response content: Unknown error. URL: ${url}. HTTP ${response.status}`,\n error: e,\n });\n });\n\n if (textResult.isErr()) {\n return textResult.error;\n }\n\n const text = textResult.value;\n const sharedDataWithText = {\n ...sharedDataWithResponse,\n text,\n };\n\n const safeParseJson = fromThrowable(JSON.parse, (e) => {\n if (e instanceof Error) {\n return err({\n type: \"jsonParseError\" as const,\n ...sharedDataWithText,\n message: `Can't parse response as JSON: ${e.message}. URL: ${url}. HTTP ${response.status}. Original response: ${text}`,\n error: e,\n });\n }\n\n return err({\n type: \"jsonParseUnknownError\" as const,\n ...sharedDataWithText,\n message: `Unknown JSON parse error. URL: ${url}. HTTP ${response.status}. Original response: ${text}`,\n error: e,\n });\n });\n\n const jsonResult = safeParseJson(text);\n\n if (jsonResult.isErr()) {\n // Try to parse as text\n const textPayload = schema.safeParse(text);\n if (textPayload.success) {\n return ok({\n ...sharedDataWithText,\n data: textPayload.data,\n }); // Payload is text\n }\n\n return err({\n type: \"jsonParseError\" as const,\n ...sharedDataWithText,\n message: `Can't parse response as JSON: ${jsonResult.error.error.message}. URL: ${url}. HTTP ${response.status}. Original response: ${text}`,\n error: jsonResult.error.error,\n });\n }\n\n const json: unknown = jsonResult.value;\n\n let errorMetadata: ErrorOut | undefined;\n\n if (errorSchema) {\n const serverError = errorSchema.safeParse(json);\n if (serverError.success) {\n errorMetadata = serverError.data;\n }\n }\n\n if (response.status >= 500) {\n // Server error\n return err({\n type: \"serverError\" as const,\n ...sharedDataWithText,\n message: `Server error. URL: ${url}. HTTP ${response.status} ${\n response.statusText\n }. Original response: ${text}. ${\n errorMetadata ? `Data: ${JSON.stringify(errorMetadata)}` : \"\"\n }`,\n });\n }\n\n if (response.status >= 400) {\n return err({\n type: \"clientError\" as const,\n ...sharedDataWithText,\n message: `Error: ${response.status} ${response.statusText}. URL: ${url}. HTTP ${response.status}. Original response: ${text}`,\n errorMetadata,\n });\n }\n\n const payload = schema.safeParse(json);\n if (!payload.success) {\n const issuesMessages = payload.error.issues\n .map((issue) => `[${issue.path.join(\".\")}] ${issue.message}`)\n .join(\", \");\n\n return err({\n type: \"payloadParseError\" as const,\n ...sharedDataWithText,\n message: `Can't recognize response payload: ${issuesMessages}. URL: ${url}. HTTP ${response.status}. Original response: ${text}`,\n });\n }\n\n return ok({\n ...sharedDataWithText,\n data: payload.data,\n });\n}\n","/* eslint-disable @typescript-eslint/naming-convention */\nimport {type z} from 'zod';\nimport type {Schema} from 'zod';\nimport fetchWithValidation from './fetchWithValidation.js';\n\n// https://stackoverflow.com/a/64919133\nclass Wrapper<DataOut, DataIn, ErrorOut, ErrorIn> {\n\tasync wrapped(\n\t\turl: string,\n\t\tschema: Schema<DataOut, z.ZodTypeDef, DataIn>,\n\t\toptions?: RequestInit,\n\t\terrorSchema?: Schema<ErrorOut, z.ZodTypeDef, ErrorIn>,\n\t) {\n\t\treturn fetchWithValidation<DataOut, DataIn, ErrorOut, ErrorIn>(url, schema, options, errorSchema);\n\t}\n}\n\nexport type FetchWithValidationType<O, I, EO, EI> = ReturnType<Wrapper<O, I, EO, EI>['wrapped']>;\n\nexport default function simpleFetch<O, I, EO, EI, P extends unknown[]>(\n\tf: (...params: P) => FetchWithValidationType<O, I, EO, EI>,\n) {\n\treturn async (...params: Parameters<typeof f>) => {\n\t\tconst result = await f(...params);\n\t\tif (result.isErr()) {\n\t\t\tconst {message, url} = result.error;\n\t\t\tthrow new Error(`${message} (${url})`);\n\t\t}\n\n\t\treturn result.value.data;\n\t};\n}\n\n// async function test() {\n// \tawait simpleFetch(\n// \t\tasync () =>\n// \t\t\tfetchWithValidation(\n// \t\t\t\t'',\n// \t\t\t\tz.object({\n// \t\t\t\t\tmessageId: z.number().int(),\n// \t\t\t\t}),\n// \t\t\t\tundefined,\n// \t\t\t\tz.object({\n// \t\t\t\t\tmessage: z.string(),\n// \t\t\t\t}),\n// \t\t\t),\n// \t)();\n// }\n"],"mappings":"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","names":["src_exports","__export","fetchWithValidation","simpleFetch","__toCommonJS","import_warp_isomorphic","import_neverthrow","fetchWithValidation","url","schema","options","errorSchema","requestOptions","sharedData","fetchResult","response","sharedDataWithResponse","textResult","text","sharedDataWithText","jsonResult","textPayload","json","errorMetadata","serverError","payload","issuesMessages","issue","simpleFetch","f","params","result","message","url"]}