fetchwire 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -150,7 +150,8 @@ function useFetchFn(fetchFn, options) {
|
|
|
150
150
|
error: null
|
|
151
151
|
});
|
|
152
152
|
const isMounted = (0, import_react.useRef)(true);
|
|
153
|
-
const
|
|
153
|
+
const fetchFnRef = (0, import_react.useRef)(fetchFn);
|
|
154
|
+
fetchFnRef.current = fetchFn;
|
|
154
155
|
(0, import_react.useEffect)(() => {
|
|
155
156
|
isMounted.current = true;
|
|
156
157
|
return () => {
|
|
@@ -159,7 +160,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
159
160
|
}, []);
|
|
160
161
|
const execute = (0, import_react.useCallback)(
|
|
161
162
|
async (execOptions) => {
|
|
162
|
-
|
|
163
|
+
const fn = fetchFnRef.current;
|
|
163
164
|
setState((prev) => ({
|
|
164
165
|
...prev,
|
|
165
166
|
isLoading: !execOptions.isRefresh,
|
|
@@ -167,7 +168,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
167
168
|
error: null
|
|
168
169
|
}));
|
|
169
170
|
try {
|
|
170
|
-
const response = await
|
|
171
|
+
const response = await fn();
|
|
171
172
|
if (isMounted.current) {
|
|
172
173
|
setState({
|
|
173
174
|
data: response.data || null,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export * from './interface';\nexport * from './util/api-error';\nexport * from './core/config';\nexport * from './core/wire';\nexport * from './hook/use-fetch-fn';\nexport * from './hook/use-mutation-fn';\n","export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const lastFetchFn = useRef<(() => Promise<HttpResponse<T>>) | null>(null);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n lastFetchFn.current = fetchFn;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fetchFn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export * from './interface';\nexport * from './util/api-error';\nexport * from './core/config';\nexport * from './core/wire';\nexport * from './hook/use-fetch-fn';\nexport * from './hook/use-mutation-fn';\n","export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const fetchFnRef = useRef(fetchFn);\n fetchFnRef.current = fetchFn;\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n const fn = fetchFnRef.current;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -118,7 +118,8 @@ function useFetchFn(fetchFn, options) {
|
|
|
118
118
|
error: null
|
|
119
119
|
});
|
|
120
120
|
const isMounted = useRef(true);
|
|
121
|
-
const
|
|
121
|
+
const fetchFnRef = useRef(fetchFn);
|
|
122
|
+
fetchFnRef.current = fetchFn;
|
|
122
123
|
useEffect(() => {
|
|
123
124
|
isMounted.current = true;
|
|
124
125
|
return () => {
|
|
@@ -127,7 +128,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
127
128
|
}, []);
|
|
128
129
|
const execute = useCallback(
|
|
129
130
|
async (execOptions) => {
|
|
130
|
-
|
|
131
|
+
const fn = fetchFnRef.current;
|
|
131
132
|
setState((prev) => ({
|
|
132
133
|
...prev,
|
|
133
134
|
isLoading: !execOptions.isRefresh,
|
|
@@ -135,7 +136,7 @@ function useFetchFn(fetchFn, options) {
|
|
|
135
136
|
error: null
|
|
136
137
|
}));
|
|
137
138
|
try {
|
|
138
|
-
const response = await
|
|
139
|
+
const response = await fn();
|
|
139
140
|
if (isMounted.current) {
|
|
140
141
|
setState({
|
|
141
142
|
data: response.data || null,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const lastFetchFn = useRef<(() => Promise<HttpResponse<T>>) | null>(null);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n lastFetchFn.current = fetchFn;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fetchFn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useEffect","useState","useRef","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../src/util/api-error.ts","../src/core/config.ts","../src/core/wire.ts","../src/hook/use-fetch-fn.ts","../src/core/event-emitter.ts","../src/hook/use-mutation-fn.ts"],"sourcesContent":["export class ApiError extends Error {\n public errorCode?: string;\n public statusCode?: number;\n\n constructor(message: string, errorCode?: string, statusCode?: number) {\n super(message);\n this.name = 'ApiError';\n this.errorCode = errorCode;\n this.statusCode = statusCode;\n }\n}","import { WireConfig } from '../interface';\n\nlet globalWireConfig: WireConfig | null = null;\n\n/**\n * Initializes the library with mandatory configurations.\n * Must be executed at the application entry point before any API calls.\n * @param config - The required configuration object including baseUrl and getToken.\n */\nexport const initWire = (config: WireConfig): void => {\n globalWireConfig = {\n ...config,\n headers: config.headers || {},\n };\n};\n\n/**\n * Updates the existing configuration.\n * Merges new headers with existing ones and overrides other provided fields.\n * @param config - A partial configuration object to update.\n */\nexport const updateWireConfig = (config: Partial<WireConfig>): void => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n\n globalWireConfig = {\n ...globalWireConfig,\n ...config,\n headers: {\n ...globalWireConfig.headers,\n ...config.headers,\n },\n };\n};\n\n/**\n * Retrieves the current global configuration state.\n * @throws Error if the configuration state is null.\n * @returns The validated WireConfig object.\n */\nexport const getWireConfig = (): WireConfig => {\n if (!globalWireConfig) {\n throw new Error('Wire not initialized. Call initWire() first.');\n }\n return globalWireConfig;\n};","import { HttpResponse } from '../interface';\nimport { ApiError } from '../util/api-error';\nimport { getWireConfig } from './config';\n\n/**\n * Sends an API request and returns the response.\n * @param endpoint - The API endpoint to call. Example: '/api/v1/users'.\n * @param options - The request options is a RequestInit object.\n */\nexport async function wireApi<T>(\n endpoint: string,\n options: RequestInit = {}\n): Promise<HttpResponse<T>> {\n const config = getWireConfig();\n const url = `${config.baseUrl}${endpoint}`;\n const accessToken = await config.getToken();\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),\n ...config.headers,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok) {\n let errorData;\n try {\n errorData = await response.json();\n } catch {\n errorData = { message: 'Unknown server error', error: 'UNKNOWN' };\n }\n\n const apiError = new ApiError(\n errorData.message,\n errorData.error,\n response.status\n );\n\n // Resolve effective status-code mappings with default values\n const unauthorizedStatusCodes =\n config.unauthorizedStatusCodes && config.unauthorizedStatusCodes.length > 0\n ? config.unauthorizedStatusCodes\n : [401];\n\n const forbiddenStatusCodes =\n config.forbiddenStatusCodes && config.forbiddenStatusCodes.length > 0\n ? config.forbiddenStatusCodes\n : [403];\n\n // Trigger interceptors based on configured status codes\n if (\n config.interceptors?.onUnauthorized &&\n unauthorizedStatusCodes.includes(response.status)\n ) {\n config.interceptors.onUnauthorized(apiError);\n } else if (\n config.interceptors?.onForbidden &&\n forbiddenStatusCodes.includes(response.status)\n ) {\n config.interceptors.onForbidden(apiError);\n } else if (config.interceptors?.onError) {\n config.interceptors.onError(apiError);\n }\n\n throw apiError;\n }\n\n return await response.json();\n } catch (error) {\n if (error instanceof ApiError) throw error;\n throw new ApiError(\n error instanceof Error ? error.message : 'Network error',\n 'NETWORK_ERROR',\n 520\n );\n }\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { ApiError } from '../util/api-error';\nimport { HttpResponse, FetchOptions } from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface FetchState<T> {\n data: T | null;\n isLoading: boolean;\n isRefreshing: boolean;\n error: ApiError | null;\n}\n\n/**\n * A hook for executing a fetch function and managing the state of the fetch.\n * @param options - Has tags property that will trigger refetching of the useFetchFn with the given tags.\n * @returns The state of the fetch and the fetch function.\n */\nexport function useFetchFn<T>(\n fetchFn: () => Promise<HttpResponse<T>>,\n options?: FetchOptions\n) {\n const [state, setState] = useState<FetchState<T>>({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n\n const isMounted = useRef<boolean>(true);\n const fetchFnRef = useRef(fetchFn);\n fetchFnRef.current = fetchFn;\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const execute = useCallback(\n async (execOptions: {\n isRefresh: boolean;\n }): Promise<HttpResponse<T> | null> => {\n const fn = fetchFnRef.current;\n\n setState((prev) => ({\n ...prev,\n isLoading: !execOptions.isRefresh,\n isRefreshing: !!execOptions.isRefresh,\n error: null,\n }));\n\n try {\n const response = await fn();\n\n if (isMounted.current) {\n setState({\n data: response.data || null,\n isLoading: false,\n isRefreshing: false,\n error: null,\n });\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isLoading: false,\n isRefreshing: false,\n error: apiError,\n });\n }\n return null;\n }\n },\n []\n );\n\n const executeFetchFn = useCallback(\n () => execute({ isRefresh: false }),\n [execute]\n );\n const refreshFetchFn = useCallback(() => execute({ isRefresh: true }), [execute]);\n\n useEffect(() => {\n if (!options?.tags || options.tags.length === 0) return;\n\n const subscriptions = options.tags.map((tag) =>\n eventEmitter.addListener(tag, () => {\n refreshFetchFn();\n })\n );\n return () => subscriptions.forEach((sub) => sub.remove());\n }, [options?.tags, refreshFetchFn]);\n\n return { ...state, executeFetchFn, refreshFetchFn };\n}\n","type Listener = () => void;\n\nclass EventEmitter {\n private events: Record<string, Listener[]> = {};\n\n emit(event: string) {\n if (!this.events[event]) return;\n this.events[event].forEach((listener) => listener());\n }\n\n addListener(event: string, listener: Listener) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n \n return {\n remove: () => {\n this.events[event] = this.events[event].filter((l) => l !== listener);\n },\n };\n }\n}\n\nexport const eventEmitter = new EventEmitter();","import { useState, useCallback, useRef, useEffect } from 'react';\nimport { ApiError } from '../util/api-error';\nimport {\n HttpResponse,\n MutationOptions,\n ExecuteMutationOptions,\n} from '../interface';\nimport { eventEmitter } from '../core/event-emitter';\n\ninterface MutationState<T> {\n data: T | null;\n isMutating: boolean;\n}\n\n/**\n * Mutation without variables. Use when the payload is fixed (e.g. from closure/state).\n *\n * @param mutationFn - Function that returns a promise (e.g. `() => createApi()`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(options?) — call with no args or only options: `executeMutationFn()` or `executeMutationFn({ onSuccess, onError })`.\n */\nexport function useMutationFn<T>(\n mutationFn: (variables: void) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\n/**\n * Mutation with variables. Use when the payload is passed at call time (e.g. update forms, PATCH body).\n *\n * @param mutationFn - Function that receives variables and returns a promise (e.g. `(data) => updateApi(id, data)`).\n * @param options - Optional `invalidatesTags` to refetch useFetchFn with those tags after success.\n * @returns executeMutationFn(variables, options?) — you must pass variables first, then optional callbacks.\n */\nexport function useMutationFn<T, TVariables>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n): {\n data: T | null;\n isMutating: boolean;\n executeMutationFn: (\n variables: TVariables,\n executeOptions?: ExecuteMutationOptions<T>\n ) => Promise<HttpResponse<T> | null>;\n reset: () => void;\n};\n\nexport function useMutationFn<T, TVariables = void>(\n mutationFn: (variables: TVariables) => Promise<HttpResponse<T>>,\n options?: MutationOptions\n) {\n const [state, setState] = useState<MutationState<T>>({\n data: null,\n isMutating: false,\n });\n const isMounted = useRef<boolean>(true);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const executeMutationFn = useCallback(\n async (\n firstArg?: TVariables | ExecuteMutationOptions<T>,\n secondArg?: ExecuteMutationOptions<T>\n ): Promise<HttpResponse<T> | null> => {\n const hasTwoArgs = secondArg !== undefined;\n const variables = (hasTwoArgs ? firstArg : undefined) as TVariables;\n const executeOptions = hasTwoArgs ? secondArg : firstArg as ExecuteMutationOptions<T>;\n\n setState((prev) => ({ ...prev, isMutating: true }));\n\n try {\n const response = await mutationFn(variables);\n\n if (isMounted.current) {\n setState({\n data: response.data ?? null,\n isMutating: false,\n });\n\n options?.invalidatesTags?.forEach((tag) => {\n eventEmitter.emit(tag);\n });\n\n if (response.data != null) executeOptions?.onSuccess?.(response.data);\n else executeOptions?.onSuccess?.(null as unknown as T);\n }\n return response;\n } catch (error) {\n const apiError = error as ApiError;\n if (isMounted.current) {\n setState({\n data: null,\n isMutating: false,\n });\n executeOptions?.onError?.(apiError);\n }\n return null;\n }\n },\n [mutationFn, options?.invalidatesTags]\n );\n\n const reset = useCallback(() => {\n setState({ data: null, isMutating: false });\n }, []);\n\n return { ...state, executeMutationFn, reset };\n}\n"],"mappings":";AAAO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAIlC,YAAY,SAAiB,WAAoB,YAAqB;AACpE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;;;ACRA,IAAI,mBAAsC;AAOnC,IAAM,WAAW,CAAC,WAA6B;AACpD,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,SAAS,OAAO,WAAW,CAAC;AAAA,EAC9B;AACF;AAOO,IAAM,mBAAmB,CAAC,WAAsC;AACrE,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAEA,qBAAmB;AAAA,IACjB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,iBAAiB;AAAA,MACpB,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AACF;AAOO,IAAM,gBAAgB,MAAkB;AAC7C,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;;;ACrCA,eAAsB,QACpB,UACA,UAAuB,CAAC,GACE;AAC1B,QAAM,SAAS,cAAc;AAC7B,QAAM,MAAM,GAAG,OAAO,OAAO,GAAG,QAAQ;AACxC,QAAM,cAAc,MAAM,OAAO,SAAS;AAE1C,QAAM,UAAuB;AAAA,IAC3B,gBAAgB;AAAA,IAChB,GAAI,cAAc,EAAE,eAAe,UAAU,WAAW,GAAG,IAAI,CAAC;AAAA,IAChE,GAAG,OAAO;AAAA,IACV,GAAG,QAAQ;AAAA,EACb;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AAEzD,QAAI,CAAC,SAAS,IAAI;AAChB,UAAI;AACJ,UAAI;AACF,oBAAY,MAAM,SAAS,KAAK;AAAA,MAClC,QAAQ;AACN,oBAAY,EAAE,SAAS,wBAAwB,OAAO,UAAU;AAAA,MAClE;AAEA,YAAM,WAAW,IAAI;AAAA,QACnB,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAGA,YAAM,0BACJ,OAAO,2BAA2B,OAAO,wBAAwB,SAAS,IACtE,OAAO,0BACP,CAAC,GAAG;AAEV,YAAM,uBACJ,OAAO,wBAAwB,OAAO,qBAAqB,SAAS,IAChE,OAAO,uBACP,CAAC,GAAG;AAGV,UACE,OAAO,cAAc,kBACrB,wBAAwB,SAAS,SAAS,MAAM,GAChD;AACA,eAAO,aAAa,eAAe,QAAQ;AAAA,MAC7C,WACE,OAAO,cAAc,eACrB,qBAAqB,SAAS,SAAS,MAAM,GAC7C;AACA,eAAO,aAAa,YAAY,QAAQ;AAAA,MAC1C,WAAW,OAAO,cAAc,SAAS;AACvC,eAAO,aAAa,QAAQ,QAAQ;AAAA,MACtC;AAEA,YAAM;AAAA,IACR;AAEA,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAU,OAAM;AACrC,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC/EA,SAAS,UAAU,QAAQ,WAAW,mBAAmB;;;ACEzD,IAAM,eAAN,MAAmB;AAAA,EAAnB;AACE,SAAQ,SAAqC,CAAC;AAAA;AAAA,EAE9C,KAAK,OAAe;AAClB,QAAI,CAAC,KAAK,OAAO,KAAK,EAAG;AACzB,SAAK,OAAO,KAAK,EAAE,QAAQ,CAAC,aAAa,SAAS,CAAC;AAAA,EACrD;AAAA,EAEA,YAAY,OAAe,UAAoB;AAC7C,QAAI,CAAC,KAAK,OAAO,KAAK,GAAG;AACvB,WAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACxB;AACA,SAAK,OAAO,KAAK,EAAE,KAAK,QAAQ;AAEhC,WAAO;AAAA,MACL,QAAQ,MAAM;AACZ,aAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,EAAE,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,eAAe,IAAI,aAAa;;;ADPtC,SAAS,WACd,SACA,SACA;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB;AAAA,IAChD,MAAM;AAAA,IACN,WAAW;AAAA,IACX,cAAc;AAAA,IACd,OAAO;AAAA,EACT,CAAC;AAED,QAAM,YAAY,OAAgB,IAAI;AACtC,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU;AAAA,IACd,OAAO,gBAEgC;AACrC,YAAM,KAAK,WAAW;AAEtB,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH,WAAW,CAAC,YAAY;AAAA,QACxB,cAAc,CAAC,CAAC,YAAY;AAAA,QAC5B,OAAO;AAAA,MACT,EAAE;AAEF,UAAI;AACF,cAAM,WAAW,MAAM,GAAG;AAE1B,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,WAAW;AAAA,YACX,cAAc;AAAA,YACd,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB;AAAA,IACrB,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;AAAA,IAClC,CAAC,OAAO;AAAA,EACV;AACA,QAAM,iBAAiB,YAAY,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;AAEhF,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,QAAQ,QAAQ,KAAK,WAAW,EAAG;AAEjD,UAAM,gBAAgB,QAAQ,KAAK;AAAA,MAAI,CAAC,QACtC,aAAa,YAAY,KAAK,MAAM;AAClC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH;AACA,WAAO,MAAM,cAAc,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC;AAAA,EAC1D,GAAG,CAAC,SAAS,MAAM,cAAc,CAAC;AAElC,SAAO,EAAE,GAAG,OAAO,gBAAgB,eAAe;AACpD;;;AElGA,SAAS,YAAAA,WAAU,eAAAC,cAAa,UAAAC,SAAQ,aAAAC,kBAAiB;AAqDlD,SAAS,cACd,YACA,SACA;AACA,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA2B;AAAA,IACnD,MAAM;AAAA,IACN,YAAY;AAAA,EACd,CAAC;AACD,QAAM,YAAYC,QAAgB,IAAI;AAEtC,EAAAC,WAAU,MAAM;AACd,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoBC;AAAA,IACxB,OACE,UACA,cACoC;AACpC,YAAM,aAAa,cAAc;AACjC,YAAM,YAAa,aAAa,WAAW;AAC3C,YAAM,iBAAiB,aAAa,YAAY;AAEhD,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,YAAY,KAAK,EAAE;AAElD,UAAI;AACF,cAAM,WAAW,MAAM,WAAW,SAAS;AAE3C,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM,SAAS,QAAQ;AAAA,YACvB,YAAY;AAAA,UACd,CAAC;AAED,mBAAS,iBAAiB,QAAQ,CAAC,QAAQ;AACzC,yBAAa,KAAK,GAAG;AAAA,UACvB,CAAC;AAED,cAAI,SAAS,QAAQ,KAAM,iBAAgB,YAAY,SAAS,IAAI;AAAA,cAC/D,iBAAgB,YAAY,IAAoB;AAAA,QACvD;AACA,eAAO;AAAA,MACT,SAAS,OAAO;AACd,cAAM,WAAW;AACjB,YAAI,UAAU,SAAS;AACrB,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,YAAY;AAAA,UACd,CAAC;AACD,0BAAgB,UAAU,QAAQ;AAAA,QACpC;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,YAAY,SAAS,eAAe;AAAA,EACvC;AAEA,QAAM,QAAQA,aAAY,MAAM;AAC9B,aAAS,EAAE,MAAM,MAAM,YAAY,MAAM,CAAC;AAAA,EAC5C,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,GAAG,OAAO,mBAAmB,MAAM;AAC9C;","names":["useState","useCallback","useRef","useEffect","useState","useRef","useEffect","useCallback"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetchwire",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A lightweight, focused API fetching library for React and React Native applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsup",
|
|
17
|
-
"dev": "tsup --watch"
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src/"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist"
|
|
@@ -27,9 +28,13 @@
|
|
|
27
28
|
"author": "Doanvinhphu",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"devDependencies": {
|
|
31
|
+
"@eslint/js": "^9.15.0",
|
|
30
32
|
"@types/react": "^19.2.14",
|
|
33
|
+
"eslint": "^9.15.0",
|
|
34
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
31
35
|
"tsup": "^8.5.1",
|
|
32
|
-
"typescript": "^5.9.3"
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"typescript-eslint": "^8.15.0"
|
|
33
38
|
},
|
|
34
39
|
"funding": [
|
|
35
40
|
{
|