UNPKG

@bgscore/react-core

Version:

A React utility library that provides advanced API hooks (fetch, mutation, caching). Built for scalable and flexible data handling in modern React applications.

540 lines (520 loc) 23.4 kB
import moment from 'moment'; import { GenericAbortSignal, AxiosProgressEvent, AxiosResponse, AxiosRequestConfig } from 'axios'; import React$1, { PropsWithChildren } from 'react'; import * as react_jsx_runtime from 'react/jsx-runtime'; type Children<T = unknown> = ChildFunction<T> | React.ReactNode; type ChildFunction<T = unknown> = (props: T) => Children<T>; interface PaginationMeta { limit: number; page: number; totalItems: number; totalPages: number; } interface ApiResponse<T = any> { status: boolean; data: T; paging?: PaginationMeta; message: string; code: number; isCancel: boolean; isUnauthorization: boolean; httpCode: number; } type CallbackHelper<T = any> = ((response: ApiResponse<T>) => (void | ApiResponse<T>)); type HandleCallback<T = any> = (props: AxiosResponse<T> & { isCancel: boolean; isUnauthorization: boolean; }, err?: any) => Omit<ApiResponse, "httpCode" | "isCancel" | "isUnauthorization">; interface UseHelperProps { /** Base URL API */ url: string; /** Header tambahan untuk request */ headers?: Record<string, string>; /** Token autentikasi, bisa string atau null/undefined jika tidak ada */ token?: string | null | undefined; /** Callback yang dijalankan setiap response atau error */ onCallback: HandleCallback; /** Handler khusus jika response tidak authorized */ onUnauthorized?: (response: ApiResponse) => void; /** Fungsi untuk memeriksa otorisasi response, return true jika authorized */ handleAuthorization?: (response: ApiResponse, options?: OptionsHelper) => boolean; /** Fungsi untuk menampilkan toast/notification berdasarkan response */ handleToast?: (response: ApiResponse) => void; /** * Fungsi manipulasi/preparasi data sebelum dikirim ke server. */ beforeRequest?: (data: any) => any; /** Jika true, toast tidak ditampilkan ketika request dibatalkan */ disabledToastWhenCancel?: boolean; /** * Jika true, maka cookie seperti HttpOnly akan dikirim dalam request. * Digunakan saat autentikasi berbasis cookie (bukan token di header Authorization). */ withCredentials?: boolean; /** * Jika `true`, data request akan dienkripsi sebelum dikirim ke server. * Pastikan server dapat mendekripsi payload ini. */ encryptRequest?: boolean; /** * Jika `true`, data response dari server akan didekripsi. * Asumsinya response telah terenkripsi sebelumnya oleh server. */ encryptResponse?: boolean; /** * Kata sandi atau kunci untuk proses enkripsi dan dekripsi data. * Digunakan bersama `encryptRequest` dan `encryptResponse` untuk memastikan * data terenkripsi dan terdekripsi dengan algoritma yang sama di sisi client dan server. * Harus disepakati antara client dan server. * * Secara default, nilai passphrase akan diambil dari context `bgsCore` jika ada. */ passphrase?: string | null; } interface OptionsHelper { /** Menampilkan toast/info ketika response success */ infoSuccess: boolean; /** Menampilkan toast/info ketika response error */ infoError: boolean; /** Menyisipkan Authorization token. * - `true` → pakai token default dari `UseHelperProps` * - `string` → pakai token string ini * - `false` → tidak menyisipkan token */ token: boolean | string; /** Header tambahan untuk request */ headers: Record<string, string>; /** Signal dari AbortController, bisa dipakai untuk cancel request */ signal: GenericAbortSignal; /** Response type seperti 'json', 'blob', dll */ responseType: ResponseType; /** Callback ketika ada upload progress (biasanya untuk form upload) */ onUploadProgress: (props: AxiosProgressEvent) => void; /** Handler saat request unauthorized (status 401/403), bisa override default */ onUnauthorized: (response: ApiResponse) => void; /** Jika `true`, maka onUnauthorized tidak akan dijalankan */ disabledHandleUnauthorized: boolean; /** Jika `true`, maka tidak akan munculkan toast saat request dibatalkan (abort) */ disabledToastWhenCancel?: boolean; /** * Jika true, maka cookie seperti HttpOnly akan dikirim dalam request. * Digunakan saat autentikasi berbasis cookie (bukan token di header Authorization). */ withCredentials?: boolean; /** * Jika `true`, data request akan dienkripsi sebelum dikirim ke server. * Pastikan server dapat mendekripsi payload ini. */ encryptRequest?: boolean; /** * Jika `true`, data response dari server akan didekripsi. * Asumsinya response telah terenkripsi sebelumnya oleh server. */ encryptResponse?: boolean; /** * Kata sandi atau kunci untuk proses enkripsi dan dekripsi data. * Digunakan bersama `encryptRequest` dan `encryptResponse` untuk memastikan * data terenkripsi dan terdekripsi dengan algoritma yang sama di sisi client dan server. * Harus disepakati antara client dan server. * * Secara default, nilai passphrase akan diambil dari context `bgsCore` jika ada. */ passphrase?: string | true | null; } type ClientCallback<T> = (response: AxiosResponse<T>, err?: any) => any; declare enum HttpMethod { POST = "POST", PUT = "PUT", PATCH = "PATCH", DELETE = "DELETE", GET = "GET" } type ApiMethod<DReq = any, DRes = DReq> = (data: DReq, callback?: CallbackHelper<DRes>, options?: Partial<OptionsHelper>) => Promise<ApiResponse<DRes>>; type ApiMethodVoid<DRes = any> = <Res = DRes>(callback?: CallbackHelper<Res>, options?: Partial<OptionsHelper>) => Promise<ApiResponse<Res>>; type ApiDefaultMethod<DReq = any, DRes = DReq> = <Req = DReq, Res = DRes>(url: string, data: Req, callback?: CallbackHelper<Res>, options?: Partial<OptionsHelper>) => Promise<ApiResponse<Res>>; type ApiDefaultFetch<DRes = any> = <Res = DRes>(url: string, callback?: CallbackHelper<Res>, options?: Partial<OptionsHelper>) => Promise<ApiResponse<Res>>; type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' | 'formdata'; type OptionsCallReturn<DReq, DRes = DReq> = Partial<ApiResponse<DRes>> & { /** Status loading saat request berlangsung */ loading: boolean; /** Menjalankan ulang request dengan payload & config sebelumnya */ refresh: (force?: boolean) => void; /** Membatalkan request yang sedang berjalan */ abort: () => void; /** Clear/reset response dan status terkait */ clear: () => void; /** Response dari server (bisa undefined/null jika belum ada atau sudah di-clear) */ response: ApiResponse<DRes> | undefined | null; /** * Mengkloning call API dengan payload/config baru. * Cocok untuk membuat instance baru tanpa mempengaruhi state lama. */ clone: <T = unknown>(newPayload?: DReq, newConfig?: Partial<UseCallOptionsProps<DReq, DRes & T>>) => UseCallReturnType<DReq, DRes>; /** Menunjukkan apakah request terakhir dibatalkan (via abort). * Berguna untuk mengabaikan cache saat refresh berikutnya. */ isCancel: boolean; }; type UseCallReturnType<DReq, DRes = DReq> = [ DRes | undefined, OptionsCallReturn<DReq, DRes> ]; interface TimeoutConfig { value: number; unit: moment.DurationInputArg2; } interface CacheProps { /** * Menentukan apakah fitur cache diaktifkan atau tidak. * Jika `false`, data tidak akan disimpan atau diambil dari cache, * meskipun properti `cacheKey` dan `cacheName` telah diatur. * Default: `true` */ enabled?: boolean; /** * Nama cache yang akan digunakan sebagai container atau namespace. * Biasanya untuk membedakan jenis cache yang berbeda. */ cacheName?: string; /** * Kunci unik untuk menyimpan dan mengambil data dari cache. * Biasanya merepresentasikan entri spesifik dalam cacheName. */ cacheKey?: string | Array<string | number | boolean | null | undefined | Record<string, any>>; /** * Durasi timeout cache sebelum dianggap kedaluwarsa. * Bisa berupa angka (dalam satuan detik) atau objek konfigurasi timeout. */ timeout?: number | TimeoutConfig; /** * Jika true, data cache akan dipertahankan walau halaman direfresh atau ditutup. * Jika false atau tidak diisi, cache akan dihapus saat halaman ditutup. */ persistence?: boolean; } interface CacheData<DRes> { /** * Waktu kedaluwarsa cache dalam bentuk ISO string. */ expired: string; /** * Data respon API yang disimpan dalam cache. */ data: ApiResponse<DRes>; } interface UseCallOptionsProps<DReq, DRes> extends OptionsHelper { /** * Jika true, proses request & response akan dicetak ke console. */ logging: boolean; /** * Fungsi manipulasi/preparasi data sebelum dikirim ke server. */ beforeRequest: (request: DReq) => DReq; /** * Fungsi manipulasi respon dari server sebelum disimpan atau ditampilkan. */ afterResponse: (response: DRes) => DRes; /** * Hook yang dijalankan sebelum request diproses. */ onBeforeRequest: (request: DReq) => void; /** * Hook yang dijalankan setelah respon diterima dari server. */ onAfterResponse: (response: ApiResponse<DRes>) => void; /** * Daftar dependency yang dapat memicu ulang pemanggilan API jika berubah. */ trigger: any[]; /** * Jika true, maka request tidak dijalankan otomatis hingga dipanggil secara manual. */ hold: boolean; /** * Callback yang dipanggil setiap kali data berubah. */ onChange: (data: DReq, options: OptionsCallReturn<DReq, DRes>) => void; /** * Mengaktifkan cache untuk menyimpan respon API. * Bisa true/false (default pengaturan dri context) atau objek konfigurasi `CacheProps`. */ cache: boolean | CacheProps; /** * Interval auto refresh data. Bisa dalam detik atau konfigurasi TimeoutConfig. */ refreshInterval: number | TimeoutConfig; /** * Menentukan apakah data akan difetch ulang saat tab aktif kembali. */ refetchOnWindowFocus: boolean; /** * Nama unik untuk store/context/cache dari request API ini. * * Properti ini digunakan sebagai identitas untuk: * - Menyimpan hasil request di global store agar bisa diakses antar komponen. * - Menamai cache secara otomatis jika `cacheName` tidak diatur manual. * - Menghindari konflik antar pemanggilan API yang berbeda. * * Disarankan menggunakan nama yang deskriptif dan unik per endpoint, * seperti: "historyAbsence", "dashboardSummary", "userProfile", dst. * * Contoh penggunaan bersama context: * const [data, options] = useApiStore(api.absence.history, "historyAbsence") */ storeName: string; } interface UseApiSendProps<Req, Res> extends Partial<OptionsHelper> { /** * Callback ketika respon berhasil. */ onSuccess?: OnCallback<Res>; /** * Callback ketika terjadi error. */ onError?: OnCallback<Res>; /** * Jika true, proses request & response akan dicetak ke console. */ logging?: boolean; /** * Fungsi manipulasi sebelum request dikirim. */ beforeRequest?: (request: Req) => Req; /** * Fungsi manipulasi setelah respon diterima. */ afterResponse?: OnCallback<Res>; /** * Jika true, request akan otomatis dibatalkan saat komponen unmount. */ abortOnUnmount?: boolean; } type OnCallback<T = any> = (response: ApiResponse<T>) => void; type ApiActionState<Res> = Partial<ApiResponse<Res>> & { /** Membatalkan request yang sedang berjalan */ abort: () => void; /** Clear/reset response dan status terkait */ reset: () => void; /** Status loading saat request berlangsung */ loading: boolean; /** Progress upload/download, nilai antara 0 - 100 */ progress: number; }; type UseApiSendReturnType<Req, Res> = Req extends undefined ? [ (values?: Req) => void, ApiActionState<Res> ] : [ (values: Req) => void, ApiActionState<Res> ]; declare enum DataTypeEnum { number = "number", currency = "currency", percent = "percent", date = "date", dateTime = "dateTime", month = "month", year = "year", time = "time", timestamp = "timestamp", string = "string", textarea = "textarea", code = "code", password = "password", boolean = "boolean", toggle = "toggle",// Optional, untuk UI switch boolean email = "email", phone = "phone", url = "url", json = "json", file = "file", image = "image", array = "array", object = "object", enum = "enum", user = "user", department = "department", status = "status" } type DataType = `${DataTypeEnum}`; type NestedKeyOf<T> = T extends object ? T extends Array<infer U> ? `${number}` | `${number}.${NestedKeyOf<U>}` : { [K in keyof T]: T[K] extends Array<infer U> ? `${K & string}` | `${K & string}[${number}]` | `${K & string}[${number}].${NestedKeyOf<U>}` : T[K] extends object ? `${K & string}` | `${K & string}.${NestedKeyOf<T[K]>}` : `${K & string}`; }[keyof T] : never; type PathValue<T, P extends string> = P extends `${infer K}[${infer I}].${infer R}` ? K extends keyof T ? T[K] extends Array<infer U> ? PathValue<U, R> : never : never : P extends `${infer K}[${infer I}]` ? K extends keyof T ? T[K] extends Array<infer U> ? U : never : never : P extends `${infer K}.${infer R}` ? K extends keyof T ? PathValue<T[K], R> : never : P extends keyof T ? T[P] : never; type SelectedNested<T> = Partial<Record<NestedKeyOf<T>, any>>; type TextTransform = "capitalize" | "uppercase" | "lowercase"; type StorePayload<DRes> = { response?: ApiResponse<DRes> | undefined | null; loading?: boolean; isCancel?: boolean; }; type OnlyAllowOptions<DReq, DRes> = DReq extends undefined ? [options?: never] : [options?: Partial<UseCallOptionsProps<DReq, DRes>>]; declare function useApiLoad<DReq, DRes>(...args: DReq extends undefined ? [api: ApiMethodVoid<DRes>, options?: Partial<UseCallOptionsProps<undefined, DRes>>] : [api: ApiMethod<DReq, DRes>, data: DReq, ...rest: OnlyAllowOptions<DReq, DRes>]): UseCallReturnType<DReq, DRes>; declare function useDelay(s: number, callback?: Function): Promise<unknown>; type OptionsNumberProps = { thouSep: string; decSep: string; decDigits: number | "auto"; }; type OptionsNumber = OptionsNumberProps & { dataType: Extract<DataType, "number">; method?: "round" | "ceil" | "floor"; }; type DataTypeDateGroup = Extract<DataType, "date" | "dateTime" | "month" | "year" | "time">; type OptionsDate = { dataType: DataTypeDateGroup; display: string; value: string; }; type OptionsBoolean = { dataType: Extract<DataType, "boolean">; trueLabel?: string; falseLabel?: string; }; type OptionsString = { dataType: Extract<DataType, "string">; textTransform?: TextTransform; ellipsis?: number; }; type UseFormattedOptions<T extends DataType> = T extends DataTypeEnum.number ? Partial<OptionsNumber> : T extends DataTypeDateGroup ? Partial<OptionsDate> : T extends DataTypeEnum.boolean ? Partial<OptionsBoolean> : T extends DataTypeEnum.string ? Partial<OptionsString> : never; type SupportedFormattedType = Extract<DataType, "number" | "date" | "dateTime" | "month" | "year" | "time" | "boolean" | "string">; declare const useFormatted: <T extends SupportedFormattedType>(value: unknown, dataType: T, options?: UseFormattedOptions<T>) => string; declare function useInterval<P extends Function>(callback: P, { interval, lead }: { interval: number; lead?: boolean; }): void; type CombinationType = "ctrl" | "alt" | "shift"; declare function useKeyPress(targetKey: string | number, combination?: CombinationType | CombinationType[], stopPropagation?: boolean): boolean; declare const createApiHelper: <DReq = any, DRes = any>({ url, token, beforeRequest, onCallback, headers: headerProps, onUnauthorized, handleToast, handleAuthorization, disabledToastWhenCancel: disabledToastWhenCancelProps, withCredentials, encryptRequest: encryptRequestDefault, encryptResponse: encryptResponseDefault, passphrase, }: UseHelperProps) => { client: (props: AxiosRequestConfig, callback?: ClientCallback<DRes>) => Promise<any>; post: ApiDefaultMethod<DReq, DRes>; put: ApiDefaultMethod<DReq, DRes>; patch: ApiDefaultMethod<DReq, DRes>; delete: ApiDefaultFetch<DRes>; get: ApiDefaultFetch<DRes>; upload: ApiDefaultMethod<DReq, DRes>; }; declare function useApiSend<DReq, DRes>(api: ApiMethod<DReq, DRes>, props?: UseApiSendProps<DReq, DRes>): UseApiSendReturnType<DReq, DRes>; declare function useApiSend<DRes>(api: ApiMethodVoid<DRes>, props?: UseApiSendProps<undefined, DRes>): UseApiSendReturnType<undefined, DRes>; interface UseScrollTriggerOptions { disableHysteresis?: boolean; target?: Node | Window | null; threshold?: number; getTrigger?: (store: ReturnType<typeof React$1.useRef<number | undefined>>, options: { disableHysteresis?: boolean; threshold?: number; target?: Node | Window | null; }) => boolean; } declare function useScrollTrigger(options?: UseScrollTriggerOptions): boolean; declare const useStorage: () => { save: <T = any>(key: string, data: T) => void; get: <T = any>(key: string) => T | null; clear: (key?: string) => void; useWatchStorage: <T = any>(key: string) => T | null; }; type EncryptedPayload = { salt: string; iv: string; encrypted: string; }; declare function encrypt<T = unknown>(payload: T, passphrase: string): EncryptedPayload; declare function decrypt<T = unknown>(cipherPayload: EncryptedPayload, passphrase: string): T; declare function encryptString(plain: string, passphrase: string): string; declare function decryptString(encryptedJson: string, passphrase: string): string; declare function useCrypto(): { passphrase: string | undefined; encrypt<T>(payload: T, passphraseProps?: string | null): EncryptedPayload; decrypt<T>(data: EncryptedPayload, passphraseProps?: string | null): T; encryptString(payload: string, passphraseProps?: string | null): string; decryptString(data: string, passphraseProps?: string | null): string; }; type InferApiRequest<T> = T extends ApiMethod<infer Req, any> ? Req : never; type InferApiResponse<T> = T extends ApiMethod<any, infer Res> ? Res : never; /** * Hook untuk menggunakan API store yang sesuai dengan method. * * @param api API method yang ingin digunakan (misalnya `api.absence.save`) * @param key (Optional) Nama store. Jika tidak diisi, akan otomatis memakai `api.__path` (jika tersedia dari `wrapApi`) atau `api.name` * * Contoh: * ```ts * const store = useApiStore(api.absence.save); // pakai __path * const store = useApiStore(api.absence.save, 'absence.save'); // pakai key manual * ``` */ declare const useApiStore: <T extends ApiMethod<any, any>>(api: T, key?: string) => UseCallReturnType<InferApiRequest<T>, InferApiResponse<T>>; type CreateElementProps<P = unknown> = { (element: (props: P) => React$1.JSX.Element, elementName: string): React$1.FC<P>; }; declare const createElement: <T>(element: (props: T) => React$1.JSX.Element, elementName: string) => React$1.FC<T>; type SplitElementResult<T extends string> = { [K in T]?: React$1.ReactNode; } & { others?: React$1.ReactNode[]; }; declare const splitElement: <T extends string>(children: React$1.ReactNode, elementNames: T[]) => SplitElementResult<T>; declare function generateUUID(): string; declare function diffJson(data1: any, data2: any): boolean; declare const mappingUndefinedtoNull: (values: any) => any; declare function isArray(data: any, length?: number): boolean; declare function isNotEmpty(value: unknown): boolean; declare const labelFormatter: { camelCase: (value?: string) => string; snackCase: (value?: string) => string; changeAll: (value?: string, capShortWords?: boolean) => string; }; declare function renderChildren<T = unknown>(children: Children, props: T): any; declare const getFieldValue: <T, P extends NestedKeyOf<T>, D = undefined>(obj: T, path: P, defaultValue?: D) => PathValue<T, P> | D; declare const sorting: { desc: <T>(data: T[], field?: NestedKeyOf<T>) => T[]; asc: <T>(data: T[], field?: NestedKeyOf<T>) => T[]; }; declare const summary: <T>(data: T[], field?: NestedKeyOf<T>) => number; declare function jsonCopy<T>(data: T): T; declare function stableStringify(obj: any): string; declare function generateCacheKey(data?: any, cacheKey?: string | any[]): string; declare function capitalizeWords(value?: string): string; declare function debounce<T extends (...args: any[]) => void>(func: T, delayInSeconds?: number): (...args: Parameters<T>) => void; type Listener = () => void; declare function createStore<T>(initialState: T): { getSnapshot: () => T; subscribe: (listener: Listener) => () => void; setState: (newState: T | ((prev: T) => T)) => void; useStore: { (): T; <K>(selector: (state: T) => K): K; }; }; type TWithName<T> = { [K in keyof T]: T[K] extends (...args: infer Args) => infer R ? ((...args: Args) => R) & { __path: string; __isVoid?: boolean; } : T[K] extends object ? TWithName<T[K]> : T[K]; }; declare function wrapApi<T extends object>(obj: T, path?: string[]): T; type FormatType = { display: string; value: string; }; type FormatContextProps = { date: FormatType; month: FormatType; year: FormatType; dateTime: FormatType; time: FormatType; number: OptionsNumberProps; }; type BgsCoreProps = { passphrase?: string; storageKey?: string; format: FormatContextProps; cache?: Omit<CacheProps, "cacheName" | "cacheKey">; }; declare function useBgsCore(): BgsCoreProps; interface BgsCoreProviderProps { value: BgsCoreProps; } declare const BgsCoreProvider: ({ children, value: options }: PropsWithChildren<BgsCoreProviderProps>) => react_jsx_runtime.JSX.Element; export { type ApiActionState, type ApiDefaultFetch, type ApiDefaultMethod, type ApiMethod, type ApiMethodVoid, type ApiResponse, type BgsCoreProps, BgsCoreProvider, type CacheData, type CacheProps, type CallbackHelper, type Children, type ClientCallback, type CombinationType, type CreateElementProps, type DataType, DataTypeEnum, type EncryptedPayload, HttpMethod, type NestedKeyOf, type OptionsCallReturn, type OptionsHelper, type OptionsNumberProps, type PaginationMeta, type PathValue, type ResponseType, type SelectedNested, type SplitElementResult, type StorePayload, type SupportedFormattedType, type TWithName, type TextTransform, type UseApiSendProps, type UseApiSendReturnType, type UseCallOptionsProps, type UseCallReturnType, type UseHelperProps, type UseScrollTriggerOptions, capitalizeWords, createApiHelper, createElement, createStore, debounce, decrypt, decryptString, diffJson, encrypt, encryptString, generateCacheKey, generateUUID, getFieldValue, isArray, isNotEmpty, jsonCopy, labelFormatter, mappingUndefinedtoNull, renderChildren, sorting, splitElement, stableStringify, summary, useApiLoad, useApiSend, useApiStore, useBgsCore, useCrypto, useDelay, useFormatted, useInterval, useKeyPress, useScrollTrigger, useStorage, wrapApi };