@navegarti/rn-design-system 0.8.5 → 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 (38) 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/components/OTPInput/index.js +24 -10
  10. package/lib/module/index.js +1 -1
  11. package/lib/typescript/src/api/errors.d.ts +98 -15
  12. package/lib/typescript/src/api/index.d.ts +13 -12
  13. package/lib/typescript/src/api/nitro-adapter.d.ts +121 -0
  14. package/lib/typescript/src/api/retry-strategy.d.ts +28 -9
  15. package/lib/typescript/src/api/stores/auth-store.d.ts +26 -3
  16. package/lib/typescript/src/api/types.d.ts +54 -20
  17. package/lib/typescript/src/api.d.ts +2 -2
  18. package/lib/typescript/src/components/Card/index.d.ts +9 -9
  19. package/lib/typescript/src/components/Card/styles.d.ts +9 -9
  20. package/lib/typescript/src/components/Carousel/components/see-all-button.d.ts +2 -1
  21. package/lib/typescript/src/components/Carousel/index.d.ts +2 -1
  22. package/lib/typescript/src/components/OTPInput/index.d.ts +1 -1
  23. package/lib/typescript/src/index.d.ts +2 -2
  24. package/package.json +30 -29
  25. package/src/api/errors.ts +99 -18
  26. package/src/api/index.ts +15 -15
  27. package/src/api/nitro-adapter.ts +357 -0
  28. package/src/api/retry-strategy.ts +45 -26
  29. package/src/api/stores/auth-store.ts +26 -3
  30. package/src/api/types.ts +61 -21
  31. package/src/api.tsx +2 -2
  32. package/src/components/Carousel/components/see-all-button.tsx +3 -1
  33. package/src/components/Carousel/index.tsx +1 -1
  34. package/src/components/OTPInput/index.tsx +15 -1
  35. package/src/index.tsx +2 -2
  36. package/lib/module/api/axios-adapter.js +0 -154
  37. package/lib/typescript/src/api/axios-adapter.d.ts +0 -57
  38. package/src/api/axios-adapter.ts +0 -239
@@ -1,10 +1,35 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Base class for all API errors
5
- * Provides common structure and debugging information
4
+ * Base class for all API errors.
5
+ * Provides common structure and debugging information for HTTP-layer failures.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * try {
10
+ * await api.get('/users/me');
11
+ * } catch (error) {
12
+ * if (error instanceof ApiError) {
13
+ * console.error(error.toDebugString());
14
+ * }
15
+ * }
16
+ * ```
6
17
  */
7
18
  export class ApiError extends Error {
19
+ /** HTTP status code associated with the error (0 for network-layer failures). */
20
+
21
+ /** URL of the request that produced this error. */
22
+
23
+ /** HTTP method of the request that produced this error. */
24
+
25
+ /** Timestamp at which the error was created. */
26
+
27
+ /**
28
+ * @param message Human-readable error description.
29
+ * @param statusCode HTTP status code (0 for network-level errors).
30
+ * @param url Request URL, if available.
31
+ * @param method HTTP method, if available.
32
+ */
8
33
  constructor(message, statusCode, url, method) {
9
34
  super(message);
10
35
  this.name = this.constructor.name;
@@ -14,13 +39,22 @@ export class ApiError extends Error {
14
39
  this.timestamp = new Date();
15
40
 
16
41
  // Maintains proper stack trace for where our error was thrown (only available on V8)
17
- if (Error.captureStackTrace) {
18
- Error.captureStackTrace(this, this.constructor);
19
- }
42
+ Error.captureStackTrace?.(this, this.constructor);
20
43
  }
21
44
 
22
45
  /**
23
- * Returns a formatted error message for debugging
46
+ * Returns a formatted multi-line string with all error details, useful for
47
+ * logging and debugging.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * console.error(error.toDebugString());
52
+ * // [AuthError] Unauthorized
53
+ * // Status: 401
54
+ * // URL: /users/me
55
+ * // Method: GET
56
+ * // Time: 2026-05-06T12:00:00.000Z
57
+ * ```
24
58
  */
25
59
  toDebugString() {
26
60
  return `[${this.name}] ${this.message}\nStatus: ${this.statusCode}\nURL: ${this.url}\nMethod: ${this.method}\nTime: ${this.timestamp.toISOString()}`;
@@ -28,26 +62,48 @@ export class ApiError extends Error {
28
62
  }
29
63
 
30
64
  /**
31
- * Network-related errors (no internet, DNS failure, timeout)
32
- * HTTP Status: N/A (network layer)
65
+ * Network-layer errors: no internet connection, DNS failure, or connection refused.
66
+ * HTTP Status: N/A (statusCode is always 0).
33
67
  */
34
68
  export class NetworkError extends ApiError {
69
+ /**
70
+ * @param message Human-readable description of the network failure.
71
+ * @param url Request URL, if available.
72
+ * @param method HTTP method, if available.
73
+ */
35
74
  constructor(message, url, method) {
36
75
  super(message, 0, url, method);
37
76
  }
38
77
  }
39
78
 
40
79
  /**
41
- * Authentication errors (401, 403)
42
- * Client lacks valid credentials or permissions
80
+ * Authentication or authorisation errors (401, 403).
81
+ * The client either lacks valid credentials or does not have permission.
82
+ *
83
+ * On a **401**, the {@link NitroAdapter} automatically clears the stored
84
+ * auth token before throwing this error.
85
+ *
86
+ * @param message Human-readable description.
87
+ * @param statusCode 401 or 403.
88
+ * @param url Request URL, if available.
89
+ * @param method HTTP method, if available.
43
90
  */
44
91
  export class AuthError extends ApiError {}
45
92
 
46
93
  /**
47
- * Client validation errors (400, 422)
48
- * Request malformed or validation failed
94
+ * Client validation errors (400, 422).
95
+ * The request was malformed or failed server-side validation.
49
96
  */
50
97
  export class ValidationError extends ApiError {
98
+ /** Field-level validation error messages keyed by field name. */
99
+
100
+ /**
101
+ * @param message Human-readable summary.
102
+ * @param statusCode 400 or 422.
103
+ * @param url Request URL, if available.
104
+ * @param method HTTP method, if available.
105
+ * @param validationErrors Optional map of field names to their error messages.
106
+ */
51
107
  constructor(message, statusCode, url, method, validationErrors) {
52
108
  super(message, statusCode, url, method);
53
109
  this.validationErrors = validationErrors;
@@ -55,34 +111,65 @@ export class ValidationError extends ApiError {
55
111
  }
56
112
 
57
113
  /**
58
- * Server errors (500, 502, 503, 504)
59
- * Something went wrong on the server
114
+ * Server errors (500, 502, 503, 504).
115
+ * Something went wrong on the server side.
116
+ *
117
+ * @param message Human-readable description.
118
+ * @param statusCode 5xx status code.
119
+ * @param url Request URL, if available.
120
+ * @param method HTTP method, if available.
60
121
  */
61
122
  export class ServerError extends ApiError {}
62
123
 
63
124
  /**
64
- * Request timeout errors
65
- * Request took too long to complete
125
+ * Request timeout errors.
126
+ * The server did not respond within the configured timeout window.
127
+ * HTTP Status: 408.
66
128
  */
67
129
  export class TimeoutError extends ApiError {
130
+ /**
131
+ * @param message Human-readable description.
132
+ * @param url Request URL, if available.
133
+ * @param method HTTP method, if available.
134
+ */
68
135
  constructor(message, url, method) {
69
136
  super(message, 408, url, method);
70
137
  }
71
138
  }
72
139
 
73
140
  /**
74
- * Resource not found errors (404)
141
+ * Resource not found errors.
142
+ * HTTP Status: 404.
75
143
  */
76
144
  export class NotFoundError extends ApiError {
145
+ /**
146
+ * @param message Human-readable description.
147
+ * @param url Request URL, if available.
148
+ * @param method HTTP method, if available.
149
+ */
77
150
  constructor(message, url, method) {
78
151
  super(message, 404, url, method);
79
152
  }
80
153
  }
81
154
 
82
155
  /**
83
- * Rate limit errors (429)
156
+ * Rate limit errors.
157
+ * The server has rejected the request because too many have been sent.
158
+ * HTTP Status: 429.
84
159
  */
85
160
  export class RateLimitError extends ApiError {
161
+ /**
162
+ * Number of seconds to wait before retrying, as advertised by the server
163
+ * in the `Retry-After` response header. May be `undefined` if the header
164
+ * was absent.
165
+ */
166
+
167
+ /**
168
+ * @param message Human-readable description.
169
+ * @param url Request URL, if available.
170
+ * @param method HTTP method, if available.
171
+ * @param retryAfter Seconds until the rate limit resets, if provided by the server.
172
+ */
86
173
  constructor(message, url, method, retryAfter) {
87
174
  super(message, 429, url, method);
88
175
  this.retryAfter = retryAfter;
@@ -1,30 +1,30 @@
1
1
  "use strict";
2
2
 
3
- import { AxiosAdapter } from "./axios-adapter.js";
3
+ import { NitroAdapter } from "./nitro-adapter.js";
4
4
 
5
5
  /**
6
- * Default API request instance
7
- * Pre-configured with default settings
6
+ * Pre-configured default adapter instance.
8
7
  *
9
- * Usage:
10
- * ```typescript
11
- * import { apiRequest } from '@navegarti/rn-design-system/api';
8
+ * The `baseURL` placeholder is intentionally generic — consuming applications
9
+ * should create their own instance (or override it) with their actual API URL:
12
10
  *
13
- * const response = await apiRequest.get<User>('/users/123');
14
- * if (response.success) {
15
- * console.log(response.data);
16
- * }
11
+ * ```ts
12
+ * import { NitroAdapter } from '@navegarti/rn-design-system/api';
13
+ *
14
+ * export const api = new NitroAdapter({
15
+ * baseURL: 'https://api.myapp.com',
16
+ * timeout: 10_000,
17
+ * });
17
18
  * ```
18
19
  */
19
- export const apiRequest = new AxiosAdapter({
20
+ export const apiRequest = new NitroAdapter({
20
21
  baseURL: 'https://api.example.com',
21
- // Will be overridden by consumers
22
- timeout: 30000,
22
+ // Override in your application
23
+ timeout: 30_000,
23
24
  retryAttempts: 3
24
25
  });
25
- export { AxiosAdapter } from "./axios-adapter.js";
26
26
  export * from "./errors.js";
27
+ export { NitroAdapter } from "./nitro-adapter.js";
27
28
  export { useAuthStore } from "./stores/auth-store.js";
28
- // Re-export types and classes for consumer use
29
29
  export * from "./types.js";
30
30
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+
3
+ import { fetch } from 'react-native-nitro-fetch';
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
+ * HTTP adapter powered by react-native-nitro-fetch.
9
+ *
10
+ * Features:
11
+ * - Automatic `Authorization: Bearer` token injection from the Zustand auth store.
12
+ * - Exponential back-off retry for transient failures (408, 429, 5xx).
13
+ * - AbortController-based request timeout with configurable duration.
14
+ * - Automatic 401 logout — clears the auth token so store subscribers can redirect.
15
+ * - Typed error hierarchy for precise error handling at call sites.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const api = new NitroAdapter({ baseURL: 'https://api.example.com' });
20
+ * api.addToken(accessToken);
21
+ *
22
+ * const user = await api.get<User>('/users/me');
23
+ * const post = await api.post<Post>('/posts', { title: 'Hello world' });
24
+ * ```
25
+ */
26
+ export class NitroAdapter {
27
+ /**
28
+ * @param config Adapter configuration: base URL, timeout, retry attempts,
29
+ * and optional default headers merged into every request.
30
+ */
31
+ constructor(config) {
32
+ this.baseURL = config.baseURL;
33
+ this.timeout = config.timeout ?? 30_000;
34
+ this.defaultHeaders = {
35
+ 'Content-Type': 'application/json',
36
+ ...config.headers
37
+ };
38
+ this.retryConfig = {
39
+ maxAttempts: config.retryAttempts ?? 3,
40
+ baseDelay: 1_000,
41
+ maxDelay: 10_000,
42
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504]
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Resolves a path or a full URL.
48
+ * Full URLs (starting with `http`) are used as-is; relative paths are
49
+ * prefixed with `baseURL`.
50
+ */
51
+ resolveUrl(url) {
52
+ return url.startsWith('http') ? url : `${this.baseURL}${url}`;
53
+ }
54
+
55
+ /**
56
+ * Builds the merged headers object for a single request.
57
+ * Merge priority (lowest → highest): defaults → auth token → per-request overrides.
58
+ * The token is read fresh from the auth store on every call.
59
+ */
60
+ buildHeaders(extra) {
61
+ const token = useAuthStore.getState().token;
62
+ const auth = token ? {
63
+ Authorization: `Bearer ${token}`
64
+ } : {};
65
+ return {
66
+ ...this.defaultHeaders,
67
+ ...auth,
68
+ ...extra
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Wraps the nitro-fetch `fetch` call with an AbortController-based timeout.
74
+ *
75
+ * - Throws {@link TimeoutError} when **our** timeout fires.
76
+ * - Propagates a user-provided `AbortSignal` so both explicit cancellation
77
+ * and timeout work simultaneously.
78
+ * - Re-throws any other error untouched.
79
+ */
80
+ async performFetch(fullUrl, init, url, method) {
81
+ const controller = new AbortController();
82
+ let didTimeout = false;
83
+ const timer = setTimeout(() => {
84
+ didTimeout = true;
85
+ controller.abort();
86
+ }, this.timeout);
87
+
88
+ // Propagate user's cancel signal so an explicit abort() also cancels the request.
89
+ const userSignal = init.signal;
90
+ if (userSignal) {
91
+ userSignal.addEventListener('abort', () => controller.abort(), {
92
+ once: true
93
+ });
94
+ }
95
+ try {
96
+ // Pass our controller's signal; nitro-fetch honours the standard AbortSignal.
97
+ return await fetch(fullUrl, {
98
+ ...init,
99
+ signal: controller.signal
100
+ });
101
+ } catch (error) {
102
+ if (didTimeout) {
103
+ throw new TimeoutError('Request timeout - server took too long to respond', url, method);
104
+ }
105
+ throw error;
106
+ } finally {
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Reads a failed response body and throws the appropriate typed error.
113
+ *
114
+ * On **401 Unauthorized**, the auth token is cleared before throwing
115
+ * {@link AuthError} so that any Zustand subscriber watching `token` can
116
+ * redirect the user to the login screen automatically.
117
+ *
118
+ * @returns This method always throws — it never resolves.
119
+ */
120
+ async parseErrorResponse(response, url, method) {
121
+ let message = 'An error occurred';
122
+ let validationErrors;
123
+ try {
124
+ const body = await response.json();
125
+ message = body.message ?? message;
126
+ validationErrors = body.errors;
127
+ } catch {
128
+ message = response.statusText || message;
129
+ }
130
+ const {
131
+ status
132
+ } = response;
133
+ if (status === 401) {
134
+ // Auto-logout: clear token so store subscribers can redirect.
135
+ useAuthStore.getState().clearToken();
136
+ throw new AuthError(message, status, url, method);
137
+ }
138
+ if (status === 403) {
139
+ throw new AuthError(message, status, url, method);
140
+ }
141
+ if (status === 404) {
142
+ throw new NotFoundError(message, url, method);
143
+ }
144
+ if (status === 429) {
145
+ const retryAfter = Number(response.headers.get('retry-after')) || undefined;
146
+ throw new RateLimitError(message, url, method, retryAfter);
147
+ }
148
+ if (status === 400 || status === 422) {
149
+ throw new ValidationError(message, status, url, method, validationErrors);
150
+ }
151
+ if (status >= 500) {
152
+ throw new ServerError(message, status, url, method);
153
+ }
154
+ throw new NetworkError(message, url, method);
155
+ }
156
+
157
+ /**
158
+ * Core request runner: resolves the URL, builds headers, executes the
159
+ * request with retry logic, and parses the response into `T`.
160
+ *
161
+ * A user-initiated `AbortError` (signal not caused by our timeout) is
162
+ * surfaced as a {@link NetworkError} with the message "Request was cancelled".
163
+ */
164
+ async executeRequest(url, method, init, config) {
165
+ const fullUrl = this.resolveUrl(url);
166
+ const headers = this.buildHeaders(config?.headers);
167
+ const mergedInit = {
168
+ ...init,
169
+ headers
170
+ };
171
+ if (config?.signal !== undefined) {
172
+ mergedInit.signal = config.signal;
173
+ }
174
+
175
+ // `cache` and `credentials` are not in RN's RequestInit declaration but are
176
+ // accepted by react-native-nitro-fetch at runtime.
177
+ const extendedInit = mergedInit;
178
+ if (config?.cache !== undefined) {
179
+ extendedInit.cache = config.cache;
180
+ }
181
+ if (config?.credentials !== undefined) {
182
+ extendedInit.credentials = config.credentials;
183
+ }
184
+ let response;
185
+ try {
186
+ response = await executeWithRetry(() => this.performFetch(fullUrl, mergedInit, url, method), this.retryConfig, (attempt, delay, error) => {
187
+ console.log(`[API Retry] Attempt ${attempt} failed, retrying in ${delay}ms…`, error.message);
188
+ });
189
+ } catch (error) {
190
+ // User-initiated cancellation (our timeout converts its AbortError to TimeoutError,
191
+ // so any remaining AbortError here came from the caller's own signal).
192
+ if (error instanceof Error && error.name === 'AbortError') {
193
+ throw new NetworkError('Request was cancelled', url, method);
194
+ }
195
+ throw error;
196
+ }
197
+ if (!response.ok) {
198
+ return this.parseErrorResponse(response, url, method);
199
+ }
200
+
201
+ // 204 No Content or non-JSON response — return undefined typed as T.
202
+ const contentType = response.headers.get('content-type');
203
+ if (response.status === 204 || !contentType?.includes('application/json')) {
204
+ return undefined;
205
+ }
206
+ return response.json();
207
+ }
208
+
209
+ /**
210
+ * Performs a GET request.
211
+ *
212
+ * @template T Expected response body type.
213
+ * @param url Request path or full URL.
214
+ * @param config Optional per-request overrides (headers, signal, cache, credentials).
215
+ */
216
+ async get(url, config) {
217
+ return this.executeRequest(url, 'GET', {
218
+ method: 'GET'
219
+ }, config);
220
+ }
221
+
222
+ /**
223
+ * Performs a POST request.
224
+ *
225
+ * @template T Expected response body type.
226
+ * @param url Request path or full URL.
227
+ * @param data Request body — will be JSON-serialised if provided.
228
+ * @param config Optional per-request overrides.
229
+ */
230
+ async post(url, data, config) {
231
+ return this.executeRequest(url, 'POST', {
232
+ method: 'POST',
233
+ body: data !== undefined ? JSON.stringify(data) : null
234
+ }, config);
235
+ }
236
+
237
+ /**
238
+ * Performs a PUT request.
239
+ *
240
+ * @template T Expected response body type.
241
+ * @param url Request path or full URL.
242
+ * @param data Request body — will be JSON-serialised if provided.
243
+ * @param config Optional per-request overrides.
244
+ */
245
+ async put(url, data, config) {
246
+ return this.executeRequest(url, 'PUT', {
247
+ method: 'PUT',
248
+ body: data !== undefined ? JSON.stringify(data) : null
249
+ }, config);
250
+ }
251
+
252
+ /**
253
+ * Performs a DELETE request.
254
+ *
255
+ * @template T Expected response body type.
256
+ * @param url Request path or full URL.
257
+ * @param config Optional per-request overrides.
258
+ */
259
+ async delete(url, config) {
260
+ return this.executeRequest(url, 'DELETE', {
261
+ method: 'DELETE'
262
+ }, config);
263
+ }
264
+
265
+ /**
266
+ * Stores an auth token and attaches it as `Authorization: Bearer <token>`
267
+ * on all subsequent requests.
268
+ *
269
+ * @param token Bearer token value.
270
+ */
271
+ addToken(token) {
272
+ useAuthStore.getState().setToken(token);
273
+ }
274
+
275
+ /**
276
+ * Clears the stored auth token, preventing it from being sent on
277
+ * subsequent requests.
278
+ */
279
+ removeToken() {
280
+ useAuthStore.getState().clearToken();
281
+ }
282
+
283
+ /**
284
+ * Returns the currently stored auth token, or `null` if none is set.
285
+ */
286
+ getToken() {
287
+ return useAuthStore.getState().token;
288
+ }
289
+ }
290
+ //# sourceMappingURL=nitro-adapter.js.map
@@ -1,30 +1,46 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Calculates exponential backoff delay
5
- * 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.
6
10
  */
7
11
  export function calculateBackoffDelay(attempt, baseDelay, maxDelay) {
8
- const exponentialDelay = baseDelay * 2 ** attempt;
9
- return Math.min(exponentialDelay, maxDelay);
12
+ return Math.min(baseDelay * 2 ** attempt, maxDelay);
10
13
  }
11
14
 
12
15
  /**
13
- * Determines if an error is retryable based on configuration
16
+ * Determines whether an error should trigger another retry attempt.
17
+ *
18
+ * Uses duck-typing on the `statusCode` property so it works with any
19
+ * error class that carries an HTTP status code — no dependency on a
20
+ * specific HTTP library's error type.
21
+ *
22
+ * @param error The thrown error.
23
+ * @param retryConfig Retry configuration.
14
24
  */
15
25
  export function isRetryableError(error, retryConfig) {
16
- // Network errors (no response) are always retryable
17
- if (!error.response) {
18
- return true;
26
+ const statusCode = error.statusCode;
27
+
28
+ // Unknown error type (e.g. user-initiated AbortError) — do not retry.
29
+ if (statusCode === undefined) {
30
+ return false;
19
31
  }
20
- const statusCode = error.response.status;
21
32
 
22
- // Check if status code is in retryable list
33
+ // Network-layer errors (statusCode 0) are always retried.
34
+ if (statusCode === 0) {
35
+ return true;
36
+ }
23
37
  return retryConfig.retryableStatusCodes.includes(statusCode);
24
38
  }
25
39
 
26
40
  /**
27
- * Sleep utility for retry delays
41
+ * Pauses execution for the specified duration.
42
+ *
43
+ * @param ms Duration in milliseconds.
28
44
  */
29
45
  export function sleep(ms) {
30
46
  return new Promise(resolve => {
@@ -33,8 +49,15 @@ export function sleep(ms) {
33
49
  }
34
50
 
35
51
  /**
36
- * Executes a request with retry logic
37
- * @template T - The expected response type
52
+ * Executes `requestFn` with exponential back-off retry logic.
53
+ * Retries only when `isRetryableError` returns `true` and there are
54
+ * remaining attempts.
55
+ *
56
+ * @template T The expected resolved type of `requestFn`.
57
+ * @param requestFn Async function to call on each attempt.
58
+ * @param retryConfig Retry strategy configuration.
59
+ * @param onRetry Optional callback invoked before each retry,
60
+ * receiving the attempt number (1-based), delay in ms, and the error.
38
61
  */
39
62
  export async function executeWithRetry(requestFn, retryConfig, onRetry) {
40
63
  let lastError;
@@ -42,27 +65,21 @@ export async function executeWithRetry(requestFn, retryConfig, onRetry) {
42
65
  try {
43
66
  return await requestFn();
44
67
  } catch (error) {
45
- const axiosError = error;
46
- lastError = axiosError;
47
-
48
- // Don't retry if not retryable or if this was the last attempt
68
+ const err = error;
69
+ lastError = err;
49
70
  const isLastAttempt = attempt === retryConfig.maxAttempts - 1;
50
- if (!isRetryableError(axiosError, retryConfig) || isLastAttempt) {
51
- throw error;
71
+ if (!isRetryableError(err, retryConfig) || isLastAttempt) {
72
+ throw err;
52
73
  }
53
-
54
- // Calculate delay and wait before retrying
55
74
  const delay = calculateBackoffDelay(attempt, retryConfig.baseDelay, retryConfig.maxDelay);
56
-
57
- // Notify callback if provided
58
75
  if (onRetry) {
59
- onRetry(attempt + 1, delay, axiosError);
76
+ onRetry(attempt + 1, delay, err);
60
77
  }
61
78
  await sleep(delay);
62
79
  }
63
80
  }
64
81
 
65
- // This should never be reached, but TypeScript needs it
82
+ // Unreachable the loop always returns or throws before exhausting attempts.
66
83
  throw lastError;
67
84
  }
68
85
  //# sourceMappingURL=retry-strategy.js.map
@@ -3,12 +3,26 @@
3
3
  import { create } from 'zustand';
4
4
 
5
5
  /**
6
- * Authentication store state
6
+ * Shape of the authentication store state.
7
7
  */
8
8
 
9
9
  /**
10
- * Zustand store for managing authentication token
11
- * Token is stored in memory and used for Authorization header
10
+ * Zustand store for managing the authentication token.
11
+ *
12
+ * The token is kept in memory and automatically injected as an
13
+ * `Authorization: Bearer` header by {@link NitroAdapter} on every request.
14
+ *
15
+ * Subscribe to `token` changes to trigger navigation to the login screen
16
+ * when the user is logged out:
17
+ *
18
+ * ```ts
19
+ * useAuthStore.subscribe(
20
+ * (state) => state.token,
21
+ * (token) => {
22
+ * if (!token) navigation.replace('Login');
23
+ * },
24
+ * );
25
+ * ```
12
26
  */
13
27
  export const useAuthStore = create(set => ({
14
28
  token: null,
@@ -1,4 +1,2 @@
1
1
  "use strict";
2
-
3
- export {};
4
2
  //# sourceMappingURL=types.js.map
package/lib/module/api.js CHANGED
@@ -6,5 +6,5 @@
6
6
  */
7
7
 
8
8
  export { ApiError, AuthError, NetworkError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError } from "./api/errors.js";
9
- export { AxiosAdapter, apiRequest, useAuthStore } from "./api/index.js";
9
+ export { apiRequest, NitroAdapter, useAuthStore } from "./api/index.js";
10
10
  //# sourceMappingURL=api.js.map