@navegarti/rn-design-system 0.8.6 → 0.8.7

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.
Files changed (35) hide show
  1. package/lib/module/api/errors.js +105 -18
  2. package/lib/module/api/index.js +15 -15
  3. package/lib/module/api/nitro-adapter.js +290 -0
  4. package/lib/module/api/retry-strategy.js +42 -25
  5. package/lib/module/api/stores/auth-store.js +17 -3
  6. package/lib/module/api/types.js +0 -2
  7. package/lib/module/api.js +1 -1
  8. package/lib/module/components/Carousel/components/see-all-button.js +2 -1
  9. package/lib/module/index.js +1 -1
  10. package/lib/typescript/src/api/errors.d.ts +98 -15
  11. package/lib/typescript/src/api/index.d.ts +13 -12
  12. package/lib/typescript/src/api/nitro-adapter.d.ts +121 -0
  13. package/lib/typescript/src/api/retry-strategy.d.ts +28 -9
  14. package/lib/typescript/src/api/stores/auth-store.d.ts +26 -3
  15. package/lib/typescript/src/api/types.d.ts +54 -20
  16. package/lib/typescript/src/api.d.ts +2 -2
  17. package/lib/typescript/src/components/Card/index.d.ts +9 -9
  18. package/lib/typescript/src/components/Card/styles.d.ts +9 -9
  19. package/lib/typescript/src/components/Carousel/components/see-all-button.d.ts +2 -1
  20. package/lib/typescript/src/components/Carousel/index.d.ts +2 -1
  21. package/lib/typescript/src/index.d.ts +2 -2
  22. package/package.json +30 -29
  23. package/src/api/errors.ts +99 -18
  24. package/src/api/index.ts +15 -15
  25. package/src/api/nitro-adapter.ts +357 -0
  26. package/src/api/retry-strategy.ts +45 -26
  27. package/src/api/stores/auth-store.ts +26 -3
  28. package/src/api/types.ts +61 -21
  29. package/src/api.tsx +2 -2
  30. package/src/components/Carousel/components/see-all-button.tsx +3 -1
  31. package/src/components/Carousel/index.tsx +1 -1
  32. package/src/index.tsx +2 -2
  33. package/lib/module/api/axios-adapter.js +0 -154
  34. package/lib/typescript/src/api/axios-adapter.d.ts +0 -57
  35. package/src/api/axios-adapter.ts +0 -239
@@ -1,39 +1,54 @@
1
- import type { AxiosError } from 'axios';
2
1
  import type { RetryConfig } from './types';
3
2
 
4
3
  /**
5
- * Calculates exponential backoff delay
6
- * Formula: min(baseDelay * (2 ^ attempt), maxDelay)
4
+ * Calculates exponential back-off delay.
5
+ * Formula: `min(baseDelay × 2^attempt, maxDelay)`
6
+ *
7
+ * @param attempt Zero-based attempt index.
8
+ * @param baseDelay Starting delay in milliseconds.
9
+ * @param maxDelay Upper ceiling for the delay in milliseconds.
7
10
  */
8
11
  export function calculateBackoffDelay(
9
12
  attempt: number,
10
13
  baseDelay: number,
11
14
  maxDelay: number,
12
15
  ): number {
13
- const exponentialDelay = baseDelay * 2 ** attempt;
14
- return Math.min(exponentialDelay, maxDelay);
16
+ return Math.min(baseDelay * 2 ** attempt, maxDelay);
15
17
  }
16
18
 
17
19
  /**
18
- * Determines if an error is retryable based on configuration
20
+ * Determines whether an error should trigger another retry attempt.
21
+ *
22
+ * Uses duck-typing on the `statusCode` property so it works with any
23
+ * error class that carries an HTTP status code — no dependency on a
24
+ * specific HTTP library's error type.
25
+ *
26
+ * @param error The thrown error.
27
+ * @param retryConfig Retry configuration.
19
28
  */
20
29
  export function isRetryableError(
21
- error: AxiosError,
30
+ error: Error,
22
31
  retryConfig: RetryConfig,
23
32
  ): boolean {
24
- // Network errors (no response) are always retryable
25
- if (!error.response) {
26
- return true;
33
+ const statusCode = (error as { statusCode?: number }).statusCode;
34
+
35
+ // Unknown error type (e.g. user-initiated AbortError) — do not retry.
36
+ if (statusCode === undefined) {
37
+ return false;
27
38
  }
28
39
 
29
- const statusCode = error.response.status;
40
+ // Network-layer errors (statusCode 0) are always retried.
41
+ if (statusCode === 0) {
42
+ return true;
43
+ }
30
44
 
31
- // Check if status code is in retryable list
32
45
  return retryConfig.retryableStatusCodes.includes(statusCode);
33
46
  }
34
47
 
35
48
  /**
36
- * Sleep utility for retry delays
49
+ * Pauses execution for the specified duration.
50
+ *
51
+ * @param ms Duration in milliseconds.
37
52
  */
38
53
  export function sleep(ms: number): Promise<void> {
39
54
  return new Promise((resolve) => {
@@ -42,45 +57,49 @@ export function sleep(ms: number): Promise<void> {
42
57
  }
43
58
 
44
59
  /**
45
- * Executes a request with retry logic
46
- * @template T - The expected response type
60
+ * Executes `requestFn` with exponential back-off retry logic.
61
+ * Retries only when `isRetryableError` returns `true` and there are
62
+ * remaining attempts.
63
+ *
64
+ * @template T The expected resolved type of `requestFn`.
65
+ * @param requestFn Async function to call on each attempt.
66
+ * @param retryConfig Retry strategy configuration.
67
+ * @param onRetry Optional callback invoked before each retry,
68
+ * receiving the attempt number (1-based), delay in ms, and the error.
47
69
  */
48
70
  export async function executeWithRetry<T>(
49
71
  requestFn: () => Promise<T>,
50
72
  retryConfig: RetryConfig,
51
- onRetry?: (attempt: number, delay: number, error: AxiosError) => void,
73
+ onRetry?: (attempt: number, delay: number, error: Error) => void,
52
74
  ): Promise<T> {
53
- let lastError: AxiosError | undefined;
75
+ let lastError: Error | undefined;
54
76
 
55
77
  for (let attempt = 0; attempt < retryConfig.maxAttempts; attempt++) {
56
78
  try {
57
79
  return await requestFn();
58
80
  } catch (error) {
59
- const axiosError = error as AxiosError;
60
- lastError = axiosError;
81
+ const err = error as Error;
82
+ lastError = err;
61
83
 
62
- // Don't retry if not retryable or if this was the last attempt
63
84
  const isLastAttempt = attempt === retryConfig.maxAttempts - 1;
64
- if (!isRetryableError(axiosError, retryConfig) || isLastAttempt) {
65
- throw error;
85
+ if (!isRetryableError(err, retryConfig) || isLastAttempt) {
86
+ throw err;
66
87
  }
67
88
 
68
- // Calculate delay and wait before retrying
69
89
  const delay = calculateBackoffDelay(
70
90
  attempt,
71
91
  retryConfig.baseDelay,
72
92
  retryConfig.maxDelay,
73
93
  );
74
94
 
75
- // Notify callback if provided
76
95
  if (onRetry) {
77
- onRetry(attempt + 1, delay, axiosError);
96
+ onRetry(attempt + 1, delay, err);
78
97
  }
79
98
 
80
99
  await sleep(delay);
81
100
  }
82
101
  }
83
102
 
84
- // This should never be reached, but TypeScript needs it
103
+ // Unreachable the loop always returns or throws before exhausting attempts.
85
104
  throw lastError;
86
105
  }
@@ -1,17 +1,40 @@
1
1
  import { create } from 'zustand';
2
2
 
3
3
  /**
4
- * Authentication store state
4
+ * Shape of the authentication store state.
5
5
  */
6
6
  interface AuthState {
7
+ /** The current Bearer token, or `null` when the user is not authenticated. */
7
8
  token: string | null;
9
+ /**
10
+ * Stores a new auth token.
11
+ * @param token Bearer token received after a successful login or refresh.
12
+ */
8
13
  setToken: (token: string) => void;
14
+ /**
15
+ * Clears the stored token, effectively logging the user out.
16
+ * Called automatically by {@link NitroAdapter} when a 401 response is received.
17
+ */
9
18
  clearToken: () => void;
10
19
  }
11
20
 
12
21
  /**
13
- * Zustand store for managing authentication token
14
- * Token is stored in memory and used for Authorization header
22
+ * Zustand store for managing the authentication token.
23
+ *
24
+ * The token is kept in memory and automatically injected as an
25
+ * `Authorization: Bearer` header by {@link NitroAdapter} on every request.
26
+ *
27
+ * Subscribe to `token` changes to trigger navigation to the login screen
28
+ * when the user is logged out:
29
+ *
30
+ * ```ts
31
+ * useAuthStore.subscribe(
32
+ * (state) => state.token,
33
+ * (token) => {
34
+ * if (!token) navigation.replace('Login');
35
+ * },
36
+ * );
37
+ * ```
15
38
  */
16
39
  export const useAuthStore = create<AuthState>((set) => ({
17
40
  token: null,
package/src/api/types.ts CHANGED
@@ -1,74 +1,114 @@
1
- import type { AxiosRequestConfig } from 'axios';
2
-
3
1
  /**
4
- * Configuration options for API adapter
2
+ * Configuration options for the API adapter.
5
3
  */
6
4
  export interface ApiConfig {
5
+ /** Base URL prepended to all relative request paths. */
7
6
  baseURL: string;
7
+ /** Request timeout in milliseconds. Defaults to 30 000 (30 s). */
8
8
  timeout?: number;
9
+ /** Maximum number of retry attempts on transient failures. Defaults to 3. */
9
10
  retryAttempts?: number;
11
+ /** Default headers merged into every request. */
12
+ headers?: Record<string, string>;
13
+ }
14
+
15
+ /**
16
+ * Per-request configuration options accepted by each HTTP method.
17
+ */
18
+ export interface RequestConfig {
19
+ /** Extra headers merged on top of the adapter's default headers. */
10
20
  headers?: Record<string, string>;
21
+ /** AbortSignal for explicit request cancellation via AbortController. */
22
+ signal?: AbortSignal;
23
+ /**
24
+ * Cache directive forwarded to the underlying fetch call.
25
+ * Supported by react-native-nitro-fetch via Cronet / URLSession.
26
+ */
27
+ cache?:
28
+ | 'default'
29
+ | 'force-cache'
30
+ | 'no-cache'
31
+ | 'no-store'
32
+ | 'only-if-cached'
33
+ | 'reload';
34
+ /** Credentials mode for cross-origin requests. */
35
+ credentials?: 'include' | 'omit' | 'same-origin';
11
36
  }
12
37
 
13
38
  /**
14
- * Interface for HTTP client adapter
15
- * Defines the contract all HTTP adapters must implement
39
+ * Contract every HTTP adapter implementation must satisfy.
16
40
  */
17
41
  export interface IHttpAdapter {
18
42
  /**
19
- * Performs a GET request
20
- * @template T - Expected response data type
43
+ * Performs a GET request.
44
+ * @template T Expected response body type.
45
+ * @param url Request path or full URL.
46
+ * @param config Optional per-request overrides.
21
47
  */
22
- get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
48
+ get<T = unknown>(url: string, config?: RequestConfig): Promise<T>;
23
49
 
24
50
  /**
25
- * Performs a POST request
26
- * @template T - Expected response data type
51
+ * Performs a POST request.
52
+ * @template T Expected response body type.
53
+ * @param url Request path or full URL.
54
+ * @param data Request body — will be JSON-serialised.
55
+ * @param config Optional per-request overrides.
27
56
  */
28
57
  post<T = unknown>(
29
58
  url: string,
30
59
  data?: unknown,
31
- config?: AxiosRequestConfig,
60
+ config?: RequestConfig,
32
61
  ): Promise<T>;
33
62
 
34
63
  /**
35
- * Performs a PUT request
36
- * @template T - Expected response data type
64
+ * Performs a PUT request.
65
+ * @template T Expected response body type.
66
+ * @param url Request path or full URL.
67
+ * @param data Request body — will be JSON-serialised.
68
+ * @param config Optional per-request overrides.
37
69
  */
38
70
  put<T = unknown>(
39
71
  url: string,
40
72
  data?: unknown,
41
- config?: AxiosRequestConfig,
73
+ config?: RequestConfig,
42
74
  ): Promise<T>;
43
75
 
44
76
  /**
45
- * Performs a DELETE request
46
- * @template T - Expected response data type
77
+ * Performs a DELETE request.
78
+ * @template T Expected response body type.
79
+ * @param url Request path or full URL.
80
+ * @param config Optional per-request overrides.
47
81
  */
48
- delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
82
+ delete<T = unknown>(url: string, config?: RequestConfig): Promise<T>;
49
83
 
50
84
  /**
51
- * Adds authentication token to all subsequent requests
85
+ * Stores an auth token that will be injected as `Authorization: Bearer <token>`
86
+ * on every subsequent request.
87
+ * @param token Bearer token value.
52
88
  */
53
89
  addToken(token: string): void;
54
90
 
55
91
  /**
56
- * Removes authentication token from requests
92
+ * Clears the stored auth token, preventing it from being sent on subsequent requests.
57
93
  */
58
94
  removeToken(): void;
59
95
 
60
96
  /**
61
- * Get token from store
97
+ * Returns the currently stored auth token, or `null` if none is set.
62
98
  */
63
99
  getToken(): string | null;
64
100
  }
65
101
 
66
102
  /**
67
- * Retry configuration
103
+ * Configuration for the exponential back-off retry strategy.
68
104
  */
69
105
  export interface RetryConfig {
106
+ /** Maximum number of attempts including the first try. */
70
107
  maxAttempts: number;
108
+ /** Base delay in milliseconds between retries. */
71
109
  baseDelay: number;
110
+ /** Upper bound on the calculated back-off delay, in milliseconds. */
72
111
  maxDelay: number;
112
+ /** HTTP status codes that trigger a retry. */
73
113
  retryableStatusCodes: number[];
74
114
  }
package/src/api.tsx CHANGED
@@ -13,5 +13,5 @@ export {
13
13
  TimeoutError,
14
14
  ValidationError,
15
15
  } from './api/errors';
16
- export { AxiosAdapter, apiRequest, useAuthStore } from './api/index';
17
- export type { ApiConfig, IHttpAdapter } from './api/types';
16
+ export { apiRequest, NitroAdapter, useAuthStore } from './api/index';
17
+ export type { ApiConfig, IHttpAdapter, RequestConfig } from './api/types';
@@ -5,17 +5,19 @@ import { Text } from '../../Text';
5
5
  import { SeeAllButton } from '../styles';
6
6
 
7
7
  type CarouselSeeAllButtonProps = {
8
+ text?: string;
8
9
  color?: string;
9
10
  } & TouchableOpacityProps;
10
11
 
11
12
  const CarouselSeeAllButton = ({
12
13
  color,
14
+ text = 'Ver todos',
13
15
  ...props
14
16
  }: CarouselSeeAllButtonProps) => {
15
17
  return (
16
18
  <SeeAllButton {...props}>
17
19
  <Text weight="bold" color={color ?? '#014661'}>
18
- Ver todos
20
+ {text}
19
21
  </Text>
20
22
  <FontAwesomeIcon
21
23
  icon={faChevronRight}
@@ -34,7 +34,7 @@ const CarouselRoot = <T,>({
34
34
 
35
35
  const listRef = useRef<FlatList<T> | null>(null);
36
36
  const activeIndexRef = useRef(defaultIndex ?? 0);
37
- const autoPlayTimerRef = useRef<NodeJS.Timeout | null>(null);
37
+ const autoPlayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
38
38
  const isUserInteracting = useRef(false);
39
39
 
40
40
  const [activeIndex, setActiveIndex] = useState(() => defaultIndex ?? 0);
package/src/index.tsx CHANGED
@@ -21,9 +21,9 @@ export {
21
21
  ValidationError,
22
22
  } from './api/errors';
23
23
 
24
- export { AxiosAdapter, apiRequest, useAuthStore } from './api/index';
24
+ export { apiRequest, NitroAdapter, useAuthStore } from './api/index';
25
25
 
26
- export type { ApiConfig, IHttpAdapter } from './api/types';
26
+ export type { ApiConfig, IHttpAdapter, RequestConfig } from './api/types';
27
27
  export type { ButtonProps } from './components/Button';
28
28
  // Components
29
29
  export { Button } from './components/Button';
@@ -1,154 +0,0 @@
1
- "use strict";
2
-
3
- import axios from 'axios';
4
- import { AuthError, NetworkError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError } from "./errors.js";
5
- import { executeWithRetry } from "./retry-strategy.js";
6
- import { useAuthStore } from "./stores/auth-store.js";
7
- /**
8
- * Axios-based HTTP adapter implementation
9
- * Implements retry logic, error handling, and token management
10
- */
11
- export class AxiosAdapter {
12
- constructor(config) {
13
- // Initialize axios instance with configuration
14
- this.axiosInstance = axios.create({
15
- baseURL: config.baseURL,
16
- timeout: config.timeout ?? 30000,
17
- // 30 seconds default
18
- headers: {
19
- 'Content-Type': 'application/json',
20
- ...config.headers
21
- }
22
- });
23
-
24
- // Configure retry behavior
25
- this.retryConfig = {
26
- maxAttempts: config.retryAttempts ?? 3,
27
- baseDelay: 1000,
28
- // 1 second
29
- maxDelay: 10000,
30
- // 10 seconds
31
- retryableStatusCodes: [408, 429, 500, 502, 503, 504]
32
- };
33
-
34
- // Setup request interceptor to inject auth token
35
- this.setupRequestInterceptor();
36
- }
37
-
38
- /**
39
- * Sets up request interceptor to automatically add auth token
40
- */
41
- setupRequestInterceptor() {
42
- this.axiosInstance.interceptors.request.use(config => {
43
- const token = useAuthStore.getState().token;
44
- if (token && config.headers) {
45
- config.headers.Authorization = `Bearer ${token}`;
46
- }
47
- return config;
48
- }, error => Promise.reject(error));
49
- }
50
-
51
- /**
52
- * Maps axios error to appropriate custom error type
53
- */
54
- mapError(error, url, method) {
55
- // Network errors (no response received)
56
- if (!error.response) {
57
- if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
58
- return new TimeoutError('Request timeout - server took too long to respond', url, method);
59
- }
60
- return new NetworkError(error.message || 'Network error - please check your connection', url, method);
61
- }
62
- const statusCode = error.response.status;
63
- const responseData = error.response.data;
64
- const message = responseData?.message || error.message || 'An error occurred';
65
-
66
- // Map status codes to error types
67
- switch (true) {
68
- case statusCode === 401 || statusCode === 403:
69
- return new AuthError(message, statusCode, url, method);
70
- case statusCode === 404:
71
- return new NotFoundError(message, url, method);
72
- case statusCode === 429:
73
- return new RateLimitError(message, url, method, Number(error.response.headers['retry-after']));
74
- case statusCode === 400 || statusCode === 422:
75
- return new ValidationError(message, statusCode, url, method, responseData?.errors);
76
- case statusCode >= 500:
77
- return new ServerError(message, statusCode, url, method);
78
- default:
79
- return new NetworkError(message, url, method);
80
- }
81
- }
82
-
83
- /**
84
- * Transforms axios response to standardized ApiResponse format
85
- */
86
- transformResponse(response) {
87
- return response.data;
88
- }
89
-
90
- /**
91
- * Executes HTTP request with retry logic and error handling
92
- */
93
- async executeRequest(requestFn, url, method) {
94
- try {
95
- const response = await executeWithRetry(requestFn, this.retryConfig, (attempt, delay, error) => {
96
- console.log(`[API Retry] Attempt ${attempt} failed, retrying in ${delay}ms...`, error.message);
97
- });
98
- return this.transformResponse(response);
99
- } catch (error) {
100
- throw this.mapError(error, url, method);
101
- }
102
- }
103
-
104
- /**
105
- * Performs GET request
106
- */
107
- async get(url, config) {
108
- return this.executeRequest(() => this.axiosInstance.get(url, config), url, 'GET');
109
- }
110
-
111
- /**
112
- * Performs POST request
113
- */
114
- async post(url, data, config) {
115
- return this.executeRequest(() => this.axiosInstance.post(url, data, config), url, 'POST');
116
- }
117
-
118
- /**
119
- * Performs PUT request
120
- */
121
- async put(url, data, config) {
122
- return this.executeRequest(() => this.axiosInstance.put(url, data, config), url, 'PUT');
123
- }
124
-
125
- /**
126
- * Performs DELETE request
127
- */
128
- async delete(url, config) {
129
- return this.executeRequest(() => this.axiosInstance.delete(url, config), url, 'DELETE');
130
- }
131
-
132
- /**
133
- * Adds authentication token to store
134
- * Token will be automatically included in all subsequent requests
135
- */
136
- addToken(token) {
137
- useAuthStore.getState().setToken(token);
138
- }
139
-
140
- /**
141
- * Removes authentication token from store
142
- */
143
- removeToken() {
144
- useAuthStore.getState().clearToken();
145
- }
146
-
147
- /**
148
- * Get token from store
149
- */
150
- getToken() {
151
- return useAuthStore.getState().token;
152
- }
153
- }
154
- //# sourceMappingURL=axios-adapter.js.map
@@ -1,57 +0,0 @@
1
- import { type AxiosRequestConfig } from 'axios';
2
- import type { ApiConfig, IHttpAdapter } from './types';
3
- /**
4
- * Axios-based HTTP adapter implementation
5
- * Implements retry logic, error handling, and token management
6
- */
7
- export declare class AxiosAdapter implements IHttpAdapter {
8
- private readonly axiosInstance;
9
- private readonly retryConfig;
10
- constructor(config: ApiConfig);
11
- /**
12
- * Sets up request interceptor to automatically add auth token
13
- */
14
- private setupRequestInterceptor;
15
- /**
16
- * Maps axios error to appropriate custom error type
17
- */
18
- private mapError;
19
- /**
20
- * Transforms axios response to standardized ApiResponse format
21
- */
22
- private transformResponse;
23
- /**
24
- * Executes HTTP request with retry logic and error handling
25
- */
26
- private executeRequest;
27
- /**
28
- * Performs GET request
29
- */
30
- get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
31
- /**
32
- * Performs POST request
33
- */
34
- post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
35
- /**
36
- * Performs PUT request
37
- */
38
- put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
39
- /**
40
- * Performs DELETE request
41
- */
42
- delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
43
- /**
44
- * Adds authentication token to store
45
- * Token will be automatically included in all subsequent requests
46
- */
47
- addToken(token: string): void;
48
- /**
49
- * Removes authentication token from store
50
- */
51
- removeToken(): void;
52
- /**
53
- * Get token from store
54
- */
55
- getToken(): string | null;
56
- }
57
- //# sourceMappingURL=axios-adapter.d.ts.map