kmod-cli 1.4.14 → 1.5.0

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.
@@ -91,7 +91,7 @@ export interface DeleteManyParams {
91
91
  meta?: AxiosRequestConfig;
92
92
  }
93
93
 
94
- export interface CustomParams {
94
+ export interface CustomParams extends AxiosRequestConfig {
95
95
  url?: string;
96
96
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete';
97
97
  payload?: any;
@@ -221,8 +221,7 @@ class DataProvider {
221
221
  options: DataProviderOptions = {}
222
222
  ) {
223
223
  this.httpClient = httpClient;
224
-
225
- // Lấy baseURL từ httpClient
224
+
226
225
  const baseURL = httpClient.defaults.baseURL || '';
227
226
  this.apiUrl = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
228
227
 
@@ -804,7 +803,8 @@ export function useOne<T = any>(
804
803
 
805
804
  export function useCreate<T = any, V = any>(
806
805
  resource: string,
807
- options: UseMutationOptions<T>
806
+ options: UseMutationOptions<T>,
807
+ meta?:AxiosRequestConfig
808
808
  ) {
809
809
  const [loading, setLoading] = useState<boolean>(false);
810
810
  const [error, setError] = useState<DataProviderError | null>(null);
@@ -819,7 +819,8 @@ export function useCreate<T = any, V = any>(
819
819
 
820
820
  try {
821
821
  const result = await dataProvider.create<T, V>(resource, {
822
- variables
822
+ variables,
823
+ meta,
823
824
  });
824
825
  if (onSuccess) {
825
826
  onSuccess(result.data);
@@ -842,7 +843,8 @@ export function useCreate<T = any, V = any>(
842
843
 
843
844
  export function useUpdate<T = any, V = any>(
844
845
  resource: string,
845
- options: UseMutationOptions<T>
846
+ options: UseMutationOptions<T>,
847
+ meta?: AxiosRequestConfig
846
848
  ) {
847
849
  const [loading, setLoading] = useState<boolean>(false);
848
850
  const [error, setError] = useState<DataProviderError | null>(null);
@@ -857,7 +859,7 @@ export function useUpdate<T = any, V = any>(
857
859
  setError(null);
858
860
 
859
861
  try {
860
- const result = await dataProvider.update<T, V>(resource, { id, variables });
862
+ const result = await dataProvider.update<T, V>(resource, { id, variables, meta });
861
863
  if (onSuccess) {
862
864
  onSuccess(result.data);
863
865
  }
@@ -881,7 +883,8 @@ export function useUpdate<T = any, V = any>(
881
883
 
882
884
  export function useDelete<T = any>(
883
885
  resource: string,
884
- options: UseMutationOptions<T>
886
+ options: UseMutationOptions<T>,
887
+ meta?: AxiosRequestConfig
885
888
  ) {
886
889
  const [loading, setLoading] = useState<boolean>(false);
887
890
  const [error, setError] = useState<DataProviderError | null>(null);
@@ -896,7 +899,7 @@ export function useDelete<T = any>(
896
899
  setError(null);
897
900
 
898
901
  try {
899
- const result = await dataProvider.deleteOne<T>(resource, { id });
902
+ const result = await dataProvider.deleteOne<T>(resource, { id, meta });
900
903
  if (onSuccess) {
901
904
  onSuccess(result.data);
902
905
  }
@@ -920,7 +923,7 @@ export function useDelete<T = any>(
920
923
 
921
924
  export function useCustom<T = any>(
922
925
  resource: string,
923
- options: UseMutationOptions<T> & CustomParams
926
+ options: UseMutationOptions<T> & CustomParams,
924
927
  ) {
925
928
  const [loading, setLoading] = useState<boolean>(false);
926
929
  const [error, setError] = useState<DataProviderError | null>(null);
@@ -942,6 +945,7 @@ export function useCustom<T = any>(
942
945
  method: options.method,
943
946
  headers: options.headers,
944
947
  query: options.query,
948
+ ...options
945
949
  });
946
950
  if (onSuccess) {
947
951
  onSuccess(result.data);
@@ -990,33 +994,70 @@ export const useDataProvider = () => {
990
994
 
991
995
  /**
992
996
  * Create HTTP client with authentication
997
+ * @param url Base URL for the HTTP client
998
+ * @param options Configuration options for the HTTP client
999
+ * @param options.tokenName Name of the token to use for authentication
1000
+ * @param options.tokenStorage Storage mechanism for the token
1001
+ * @param options.authorizationType Type of authorization (e.g., Bearer, Basic)
1002
+ * @param options.withCredentials Whether to send cookies with requests (defaults to true if tokenStorage is "http-only")
1003
+ * @returns Configured Axios instance
993
1004
  */
994
- export function createHttpClient(
995
- baseURL: string,
996
- authTokenKey: string = 'token',
997
- authTokenStorage: 'localStorage' | 'sessionStorage' | 'cookie' = 'cookie',
998
- typeAuthorization: "Bearer" | "Basic" | string = "Bearer"
999
- ): AxiosInstance {
1005
+
1006
+ export interface ICreateHttpClientOptions {
1007
+ tokenName?: string ;
1008
+ tokenStorage?: "local" | "session" | "cookie" | "http-only";
1009
+ authorizationType?: "Bearer" | "Basic" | string;
1010
+ withCredentials?: boolean;
1011
+ }
1012
+
1013
+ export interface ICreateHttpClient {
1014
+ url?: string;
1015
+ options?: ICreateHttpClientOptions
1016
+ }
1017
+ export function createHttpClient({url, options = {}}:ICreateHttpClient): AxiosInstance {
1018
+ const {
1019
+ tokenName = "token",
1020
+ tokenStorage = "http-only",
1021
+ authorizationType = "Bearer",
1022
+ } = options;
1023
+
1024
+ const withCredentials =
1025
+ options.withCredentials ?? tokenStorage === "http-only";
1026
+
1027
+
1000
1028
  const axiosInstance = axios.create({
1001
- baseURL: baseURL || 'https://api.example.com',
1029
+ baseURL: url || "https://api.example.com",
1030
+ withCredentials,
1002
1031
  });
1003
1032
 
1004
- axiosInstance.interceptors.request.use(config => {
1033
+ axiosInstance.interceptors.request.use((config) => {
1005
1034
  let token: string | null = null;
1006
-
1007
- if (authTokenStorage === 'localStorage') {
1008
- token = localStorage.getItem(authTokenKey);
1009
- } else if (authTokenStorage === 'sessionStorage') {
1010
- token = sessionStorage.getItem(authTokenKey);
1011
- } else if (authTokenStorage === 'cookie') {
1012
- const match = document.cookie.match(new RegExp('(^| )' + authTokenKey + '=([^;]+)'));
1013
- if (match) token = match[2];
1035
+
1036
+ switch (options.tokenStorage) {
1037
+ case "local":
1038
+ token = localStorage.getItem(tokenName);
1039
+ break;
1040
+
1041
+ case "session":
1042
+ token = sessionStorage.getItem(tokenName);
1043
+ break;
1044
+
1045
+ case "cookie":
1046
+ token = cookiesProvider.get(tokenName) ?? null;
1047
+ break;
1048
+
1049
+ case "http-only":
1050
+ // NO READ
1051
+ // HttpOnly cookies are not accessible via JavaScript
1052
+ // Browser will send it automatically
1053
+ break;
1014
1054
  }
1015
-
1055
+
1056
+ // Just set token if available in storage
1016
1057
  if (token) {
1017
- config.headers.Authorization = `${typeAuthorization} ${token}`;
1018
- }
1019
-
1058
+ config.headers.Authorization = `${authorizationType} ${token}`;
1059
+ }
1060
+
1020
1061
  return config;
1021
1062
  });
1022
1063
 
@@ -1046,15 +1087,36 @@ interface AuthContextValue {
1046
1087
  isAuthenticated: () => boolean;
1047
1088
  setUser: (user: AuthUser | null) => void;
1048
1089
  login: (payload: LoginPayload, type?: "full" | "simple") => Promise<any>;
1049
- logout: () => void;
1090
+ logout: (params: CustomParams, type?: TypeResponse) => Promise<any>;
1050
1091
  getMe: (type?: "full" | "simple") => Promise<any>;
1051
1092
  }
1052
1093
 
1094
+ /**
1095
+ * Authentication Provider Urls Props
1096
+ * @param loginUrl - URL for login API
1097
+ * @param logoutUrl - URL for logout API
1098
+ * @param meUrl - URL for fetching current user info
1099
+ * @returns AuthProviderUrls
1100
+ */
1101
+ export interface AuthProviderUrls {
1102
+ loginUrl: string;
1103
+ logoutUrl?: string;
1104
+ meUrl?: string;
1105
+ }
1106
+
1107
+ /**
1108
+ * Authentication Provider Props
1109
+ * @param children - React children nodes
1110
+ * @param urls - AuthProviderUrls
1111
+ * @param tokenKey - Key for token storage
1112
+ * @param keysCleanUpOnLogout - Additional keys to clean up on logout
1113
+ * @returns AuthProviderProps
1114
+ */
1053
1115
  export interface AuthProviderProps {
1054
1116
  children: React.ReactNode;
1055
- loginUrl: string;
1056
- meUrl: string;
1117
+ urls: AuthProviderUrls;
1057
1118
  tokenKey: string;
1119
+ keysCleanUpOnLogout?: string[] | string;
1058
1120
  }
1059
1121
 
1060
1122
  export type TypeResponse = "full" | "simple";
@@ -1071,13 +1133,21 @@ export const useAuth = () => {
1071
1133
 
1072
1134
  export const AuthProvider: React.FC<AuthProviderProps> = ({
1073
1135
  children,
1074
- loginUrl = '/auth/login',
1075
- meUrl = '/auth/me',
1136
+ urls,
1076
1137
  tokenKey = 'token',
1138
+ keysCleanUpOnLogout = ["token"],
1077
1139
  }) => {
1078
1140
  const dataProvider = useDataProvider();
1079
1141
  const [user, setUser] = useState<AuthUser | null>(null);
1080
1142
 
1143
+ const { loginUrl, meUrl, logoutUrl } = urls;
1144
+
1145
+ function removeKeys(key: string) {
1146
+ cookiesProvider.remove(key);
1147
+ localStorage.removeItem(key);
1148
+ sessionStorage.removeItem(key);
1149
+ }
1150
+
1081
1151
  const login = useCallback(async (payload: LoginPayload, type: TypeResponse = "full") => {
1082
1152
  try {
1083
1153
  const res = await dataProvider.custom<any>({
@@ -1096,15 +1166,37 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({
1096
1166
  }
1097
1167
  , [dataProvider, loginUrl]);
1098
1168
 
1099
- const logout = useCallback(() => {
1100
- cookiesProvider.remove(tokenKey);
1101
- localStorage.clear();
1102
- sessionStorage.clear();
1103
- dataProvider.clearAllCache();
1104
- }, [dataProvider]);
1169
+ const logout = useCallback(async (params: CustomParams, type: TypeResponse = "full") => {
1170
+
1171
+ try {
1172
+ const res = await dataProvider.custom<any>({
1173
+ url: params.url || logoutUrl,
1174
+ ...params,
1175
+ });
1176
+
1177
+ if (type === "simple") {
1178
+ return res.data;
1179
+ }
1180
+
1181
+ return res;
1182
+ } catch (error) {
1183
+ throw error;
1184
+ } finally {
1185
+ setUser(null);
1186
+ dataProvider.clearAllCache();
1187
+ if(Array.isArray(keysCleanUpOnLogout)){
1188
+ keysCleanUpOnLogout.forEach((key) => {
1189
+ removeKeys(key);
1190
+ });
1191
+ } else {
1192
+ removeKeys(keysCleanUpOnLogout);
1193
+ }
1194
+ }
1195
+
1196
+ }, [dataProvider, logoutUrl]);
1105
1197
 
1106
1198
  const isAuthenticated = () => {
1107
- return (cookiesProvider.get(tokenKey) !== null && cookiesProvider.get(tokenKey) !== undefined && cookiesProvider.get(tokenKey) !== "" && typeof cookiesProvider.get(tokenKey) === "string" && user !== null) ? true : false;
1199
+ return user !== null;
1108
1200
  };
1109
1201
  const getToken = useCallback(() => {
1110
1202
  return cookiesProvider.get(tokenKey);
@@ -1124,12 +1216,17 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({
1124
1216
  } catch {
1125
1217
  return null;
1126
1218
  }
1127
- }, [dataProvider, meUrl, logout]);
1219
+ }, [dataProvider, meUrl]);
1220
+
1221
+ useEffect(() => {
1222
+ getMe().then((u: any) => u && setUser(u));
1223
+ }, [getMe]);
1224
+
1128
1225
 
1129
1226
  const value: AuthContextValue = {
1130
1227
  user,
1131
1228
  setUser,
1132
- token: getToken(),
1229
+ token: getToken() ?? null,
1133
1230
  isAuthenticated: isAuthenticated,
1134
1231
  login,
1135
1232
  logout,
@@ -1167,28 +1264,39 @@ export const cookiesProvider = {
1167
1264
 
1168
1265
  // =================== Example ===================
1169
1266
 
1170
- // create httpClient
1267
+ // create httpClient - (can create multiple httpClients for different apis)
1171
1268
 
1172
1269
  // const TOKEN = "token";
1173
1270
 
1174
- // const httpClient = createHttpClient(
1175
- // `${process.env.NEXT_PUBLIC_API_URL}`,
1176
- // TOKEN, --> key_name_cookie
1177
- // "cookie", --> storage
1178
- // "Bearer" --> prefix
1179
- // );
1271
+ // const httpClient = createHttpClient({
1272
+ // url: `${process.env.NEXT_PUBLIC_API_URL}`,
1273
+ // options: {
1274
+ // authorizationType: "Bearer",
1275
+ // tokenName: TOKEN,
1276
+ // tokenStorage: "cookie",
1277
+ // withCredentials: true, --- optionals (default to true if tokenStorage is "http-only")
1278
+ // },
1279
+ // });
1180
1280
 
1181
1281
 
1182
1282
  // create dataProvider
1183
1283
 
1184
1284
  // const dataProvider = useDataProvider(httpClient);
1185
1285
 
1286
+ // const urls = {
1287
+ // loginUrl: "/auth/login", --> api_login
1288
+ // logoutUrl: "/auth/logout", --> api_logout
1289
+ // meUrl: "/auth/me", --> api_get_me_by_token
1290
+ // }
1291
+
1292
+ // const keysRemoveOnLogout = [TOKEN, "refreshToken", "user"];
1293
+
1186
1294
  // wrapped all into:
1187
1295
  // <DataProvider dataProvider={dataProvider}>
1188
1296
  // <AuthProvider
1189
- // loginUrl={"/auth/login"} --> api_login
1297
+ // urls={urls} --> api_login
1190
1298
  // tokenKey={TOKEN}
1191
- // meUrl='/auth/me' --> api_get_me_by_token
1299
+ // keysCleanUpOnLogout={keysRemoveOnLogout} --> optional (default to ["token"]) - additional keys to clean up on logout
1192
1300
  // >
1193
1301
  // <App />
1194
1302
  // </AuthProvider>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kmod-cli",
3
- "version": "1.4.14",
3
+ "version": "1.5.0",
4
4
  "description": "Stack components utilities fast setup in projects",
5
5
  "author": "kumo_d",
6
6
  "license": "MIT",