@thorprovider/adapters 3.0.2

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 (95) hide show
  1. package/.turbo/turbo-build.log +18 -0
  2. package/.turbo/turbo-test.log +1 -0
  3. package/.turbo/turbo-type-check.log +0 -0
  4. package/AGENTS.md +1 -0
  5. package/CHANGELOG.md +271 -0
  6. package/README.md +634 -0
  7. package/dist/.tsbuildinfo +1 -0
  8. package/dist/index.d.mts +2524 -0
  9. package/dist/index.d.ts +2524 -0
  10. package/dist/index.js +8136 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/index.mjs +8045 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/docs/RETRY_SOLUTION_SUMMARY.md +55 -0
  15. package/docs/authentication.md +165 -0
  16. package/docs/capabilities-system.md +143 -0
  17. package/docs/category-customization.md +309 -0
  18. package/docs/fetch-reliability-solution.md +399 -0
  19. package/docs/getting-started.md +154 -0
  20. package/docs/multi-tenant-channel-scoping.md +225 -0
  21. package/docs/profile-management.md +147 -0
  22. package/docs/providers-overview.md +85 -0
  23. package/docs/services-overview.md +135 -0
  24. package/docs/update-documentation.md +113 -0
  25. package/package.json +43 -0
  26. package/publish.log +93 -0
  27. package/quick-reference.md +169 -0
  28. package/src/admin/AdminAuthService.ts +88 -0
  29. package/src/admin/AdminSessionService.ts +91 -0
  30. package/src/admin/MetricsService.ts +145 -0
  31. package/src/admin/index.ts +13 -0
  32. package/src/backend/BackendMetadataAdapter.ts +88 -0
  33. package/src/backend/factory.ts +107 -0
  34. package/src/backend/index.ts +12 -0
  35. package/src/backend/medusa/MedusaMetadataAdapter.ts +139 -0
  36. package/src/core/errors.js +161 -0
  37. package/src/core/errors.ts +132 -0
  38. package/src/core/fetch-utils.js +240 -0
  39. package/src/core/fetch-utils.ts +227 -0
  40. package/src/core/index.ts +8 -0
  41. package/src/core/logger.js +63 -0
  42. package/src/core/logger.ts +37 -0
  43. package/src/factory/index.ts +102 -0
  44. package/src/index.ts +205 -0
  45. package/src/lib/money-transform.ts +80 -0
  46. package/src/metadata/channel-scope-validation.js +136 -0
  47. package/src/metadata/channel-scope-validation.ts +151 -0
  48. package/src/metadata/channel-scope.js +174 -0
  49. package/src/metadata/channel-scope.ts +215 -0
  50. package/src/metadata/index.js +31 -0
  51. package/src/metadata/index.ts +37 -0
  52. package/src/providers/bigcommerce/bigcommerce.ts +6 -0
  53. package/src/providers/bigcommerce/index.ts +315 -0
  54. package/src/providers/bigcommerce/transforms.ts +227 -0
  55. package/src/providers/index.ts +22 -0
  56. package/src/providers/magento/index.ts +387 -0
  57. package/src/providers/magento/magento.ts +5 -0
  58. package/src/providers/magento/queries.ts +267 -0
  59. package/src/providers/magento/transforms.ts +249 -0
  60. package/src/providers/medusa/auth.js +601 -0
  61. package/src/providers/medusa/auth.ts +496 -0
  62. package/src/providers/medusa/fulfillment-planner.ts +225 -0
  63. package/src/providers/medusa/index.js +2485 -0
  64. package/src/providers/medusa/index.ts +4239 -0
  65. package/src/providers/medusa/medusa.ts +8 -0
  66. package/src/providers/medusa/payment.js +265 -0
  67. package/src/providers/medusa/payment.ts +269 -0
  68. package/src/providers/medusa/transforms.js +505 -0
  69. package/src/providers/medusa/transforms.ts +625 -0
  70. package/src/providers/shopify/index.ts +444 -0
  71. package/src/providers/shopify/mutations.ts +102 -0
  72. package/src/providers/shopify/queries.ts +324 -0
  73. package/src/providers/shopify/shopify.ts +7 -0
  74. package/src/providers/shopify/transforms.ts +270 -0
  75. package/src/providers/spree/index.ts +344 -0
  76. package/src/providers/spree/spree.ts +5 -0
  77. package/src/providers/spree/transforms.ts +254 -0
  78. package/src/providers/woocommerce/index.ts +310 -0
  79. package/src/providers/woocommerce/transforms.ts +254 -0
  80. package/src/providers/woocommerce/woocommerce.ts +5 -0
  81. package/src/resilience/README.md +299 -0
  82. package/src/resilience/circuit-breaker.ts +222 -0
  83. package/src/resilience/index.ts +10 -0
  84. package/src/types/capabilities.types.js +141 -0
  85. package/src/types/capabilities.types.ts +366 -0
  86. package/src/types/index.ts +24 -0
  87. package/src/types/provider.js +9 -0
  88. package/src/types/provider.ts +9 -0
  89. package/src/types/ui-responses.ts +261 -0
  90. package/src/utils/city-coordinates.ts +40 -0
  91. package/src/utils/geolocation.ts +52 -0
  92. package/src/utils/payment-utils.js +152 -0
  93. package/src/utils/payment-utils.ts +203 -0
  94. package/tsconfig.json +15 -0
  95. package/tsup.config.ts +14 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Core errors
4
+ */
5
+
6
+ /**
7
+ * Base error class for commerce providers
8
+ */
9
+ export class CommerceError extends Error {
10
+ constructor(
11
+ message: string,
12
+ public readonly code?: string,
13
+ public readonly status?: number
14
+ ) {
15
+ super(message);
16
+ this.name = 'CommerceError';
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Provider not found error
22
+ */
23
+ export class ProviderNotFoundError extends CommerceError {
24
+ constructor(providerName: string) {
25
+ super(`Provider not found: ${providerName}`, 'PROVIDER_NOT_FOUND');
26
+ this.name = 'ProviderNotFoundError';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Configuration error
32
+ */
33
+ export class ConfigurationError extends CommerceError {
34
+ constructor(message: string) {
35
+ super(message, 'CONFIGURATION_ERROR');
36
+ this.name = 'ConfigurationError';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Resource not found error
42
+ */
43
+ export class ResourceNotFoundError extends CommerceError {
44
+ constructor(resource: string, identifier: string) {
45
+ super(`${resource} not found: ${identifier}`, 'RESOURCE_NOT_FOUND', 404);
46
+ this.name = 'ResourceNotFoundError';
47
+ }
48
+ }
49
+
50
+ /**
51
+ * API error from provider
52
+ */
53
+ export class ProviderAPIError extends CommerceError {
54
+ constructor(
55
+ message: string,
56
+ public readonly provider: string,
57
+ status?: number
58
+ ) {
59
+ super(message, 'PROVIDER_API_ERROR', status);
60
+ this.name = 'ProviderAPIError';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Extract error message from various error types
66
+ * Handles standard Error objects, API responses, and unknown error types
67
+ */
68
+ export function extractErrorMessage(error: unknown): string {
69
+ // Standard Error object
70
+ if (error instanceof Error && error.message) {
71
+ // Don't return generic messages if there's more data
72
+ if (error.message !== 'An unknown error occurred.') {
73
+ return error.message;
74
+ }
75
+ }
76
+
77
+ // Object with message property
78
+ if (typeof error === 'object' && error !== null) {
79
+ const err = error as any;
80
+
81
+ // Check for response body (common in fetch errors)
82
+ if (err.body) {
83
+ if (typeof err.body === 'string') return err.body;
84
+ if (err.body.message) return String(err.body.message);
85
+ if (err.body.error?.message) return String(err.body.error.message);
86
+ }
87
+
88
+ // Check for response object
89
+ if (err.response) {
90
+ if (err.response.data?.message) return String(err.response.data.message);
91
+ if (err.response.data?.error) return String(err.response.data.error);
92
+ if (err.response.statusText) return `${err.response.status || ''} ${err.response.statusText}`.trim();
93
+ }
94
+
95
+ // Check for data property
96
+ if (err.data?.message) return String(err.data.message);
97
+ if (err.data?.error) return String(err.data.error);
98
+
99
+ // Check for error property
100
+ if (err.error?.message) return String(err.error.message);
101
+
102
+ // Check for statusText with status code
103
+ if (err.status && err.statusText) {
104
+ return `HTTP ${err.status}: ${err.statusText}`;
105
+ }
106
+
107
+ if (err.status && !err.statusText) {
108
+ return `HTTP ${err.status} Error`;
109
+ }
110
+
111
+ // Check for message property (not generic)
112
+ if (err.message && err.message !== 'An unknown error occurred.') {
113
+ return String(err.message);
114
+ }
115
+
116
+ // Try to stringify the error object
117
+ try {
118
+ const stringified = JSON.stringify(error);
119
+ if (stringified !== '{}') return stringified;
120
+ } catch {
121
+ // Ignore JSON.stringify errors
122
+ }
123
+ }
124
+
125
+ // String error
126
+ if (typeof error === 'string') {
127
+ return error;
128
+ }
129
+
130
+ // Fallback
131
+ return 'An unknown error occurred';
132
+ }
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ /**
3
+ * @thorprovider/adapters v2.0
4
+ * Fetch Utilities - Retry logic, timeouts, and error handling
5
+ *
6
+ * This module provides enhanced fetch capabilities with:
7
+ * - Automatic retries with exponential backoff
8
+ * - Configurable timeouts
9
+ * - Better error handling for network failures
10
+ */
11
+ var __assign = (this && this.__assign) || function () {
12
+ __assign = Object.assign || function(t) {
13
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
14
+ s = arguments[i];
15
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
16
+ t[p] = s[p];
17
+ }
18
+ return t;
19
+ };
20
+ return __assign.apply(this, arguments);
21
+ };
22
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
23
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
24
+ return new (P || (P = Promise))(function (resolve, reject) {
25
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
26
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
27
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
28
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
29
+ });
30
+ };
31
+ var __generator = (this && this.__generator) || function (thisArg, body) {
32
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
33
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
34
+ function verb(n) { return function (v) { return step([n, v]); }; }
35
+ function step(op) {
36
+ if (f) throw new TypeError("Generator is already executing.");
37
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
38
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
39
+ if (y = 0, t) op = [op[0] & 2, t.value];
40
+ switch (op[0]) {
41
+ case 0: case 1: t = op; break;
42
+ case 4: _.label++; return { value: op[1], done: false };
43
+ case 5: _.label++; y = op[1]; op = [0]; continue;
44
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
45
+ default:
46
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
47
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
48
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
49
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
50
+ if (t[2]) _.ops.pop();
51
+ _.trys.pop(); continue;
52
+ }
53
+ op = body.call(thisArg, _);
54
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
55
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
56
+ }
57
+ };
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.fetchWithRetry = fetchWithRetry;
60
+ exports.createFetchWithRetry = createFetchWithRetry;
61
+ var logger_1 = require("./logger");
62
+ var logger = (0, logger_1.createLogger)('FetchUtils');
63
+ var DEFAULT_RETRY_CONFIG = {
64
+ maxRetries: 3,
65
+ initialDelayMs: 300,
66
+ backoffMultiplier: 2,
67
+ maxDelayMs: 5000,
68
+ timeoutMs: 10000, // 10 seconds
69
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504],
70
+ };
71
+ /**
72
+ * Sleep utility for delays between retries
73
+ */
74
+ function sleep(ms) {
75
+ return new Promise(function (resolve) { return setTimeout(resolve, ms); });
76
+ }
77
+ /**
78
+ * Calculate delay for exponential backoff
79
+ */
80
+ function calculateDelay(attempt, config) {
81
+ var delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
82
+ return Math.min(delay, config.maxDelayMs);
83
+ }
84
+ /**
85
+ * Check if error is retryable
86
+ */
87
+ function isRetryableError(error, config) {
88
+ var _a, _b;
89
+ // Network errors (fetch failed, connection refused, etc.)
90
+ if (error.name === 'TypeError' || ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))) {
91
+ return true;
92
+ }
93
+ // Timeout errors
94
+ if (error.name === 'AbortError' || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('timeout'))) {
95
+ return true;
96
+ }
97
+ // HTTP status codes
98
+ if (error.status && config.retryableStatusCodes.includes(error.status)) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ /**
104
+ * Enhanced fetch with retry logic and timeout
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * import { fetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
109
+ *
110
+ * // Basic usage with defaults (3 retries, 10s timeout)
111
+ * const response = await fetchWithRetry('https://api.example.com/products');
112
+ *
113
+ * // Custom configuration
114
+ * const response = await fetchWithRetry(
115
+ * 'https://api.example.com/products',
116
+ * {
117
+ * method: 'POST',
118
+ * body: JSON.stringify({ name: 'Product' }),
119
+ * },
120
+ * {
121
+ * maxRetries: 5,
122
+ * timeoutMs: 15000, // 15 seconds
123
+ * initialDelayMs: 500,
124
+ * }
125
+ * );
126
+ * ```
127
+ */
128
+ function fetchWithRetry(url, init, retryConfig) {
129
+ return __awaiter(this, void 0, void 0, function () {
130
+ var config, lastError, _loop_1, attempt, state_1;
131
+ return __generator(this, function (_a) {
132
+ switch (_a.label) {
133
+ case 0:
134
+ config = __assign(__assign({}, DEFAULT_RETRY_CONFIG), retryConfig);
135
+ _loop_1 = function (attempt) {
136
+ var controller_1, timeoutId, fetchOptions, response, error, error_1, shouldRetry, delay;
137
+ return __generator(this, function (_b) {
138
+ switch (_b.label) {
139
+ case 0:
140
+ _b.trys.push([0, 2, , 6]);
141
+ controller_1 = new AbortController();
142
+ timeoutId = setTimeout(function () { return controller_1.abort(); }, config.timeoutMs);
143
+ fetchOptions = __assign(__assign({}, init), { signal: controller_1.signal });
144
+ logger.debug('Fetch attempt', {
145
+ url: url,
146
+ attempt: attempt + 1,
147
+ maxAttempts: config.maxRetries + 1,
148
+ timeout: config.timeoutMs,
149
+ });
150
+ return [4 /*yield*/, fetch(url, fetchOptions)];
151
+ case 1:
152
+ response = _b.sent();
153
+ // Clear timeout
154
+ clearTimeout(timeoutId);
155
+ // Check if status code is retryable
156
+ if (!response.ok && config.retryableStatusCodes.includes(response.status)) {
157
+ error = new Error("HTTP ".concat(response.status, ": ").concat(response.statusText));
158
+ error.status = response.status;
159
+ throw error;
160
+ }
161
+ // Success!
162
+ logger.debug('Fetch successful', { url: url, attempt: attempt + 1 });
163
+ return [2 /*return*/, { value: response }];
164
+ case 2:
165
+ error_1 = _b.sent();
166
+ lastError = error_1;
167
+ // Clear timeout if it exists
168
+ if (error_1.name === 'AbortError') {
169
+ lastError = new Error("Request timeout after ".concat(config.timeoutMs, "ms"));
170
+ lastError.name = 'TimeoutError';
171
+ }
172
+ shouldRetry = attempt < config.maxRetries && isRetryableError(error_1, config);
173
+ if (!shouldRetry) return [3 /*break*/, 4];
174
+ delay = calculateDelay(attempt, config);
175
+ logger.warn('Fetch failed, retrying...', {
176
+ url: url,
177
+ attempt: attempt + 1,
178
+ maxAttempts: config.maxRetries + 1,
179
+ nextRetryIn: "".concat(delay, "ms"),
180
+ error: error_1.message,
181
+ });
182
+ return [4 /*yield*/, sleep(delay)];
183
+ case 3:
184
+ _b.sent();
185
+ return [3 /*break*/, 5];
186
+ case 4:
187
+ logger.error('Fetch failed after all retries', {
188
+ url: url,
189
+ totalAttempts: attempt + 1,
190
+ error: error_1.message,
191
+ });
192
+ return [2 /*return*/, "break"];
193
+ case 5: return [3 /*break*/, 6];
194
+ case 6: return [2 /*return*/];
195
+ }
196
+ });
197
+ };
198
+ attempt = 0;
199
+ _a.label = 1;
200
+ case 1:
201
+ if (!(attempt <= config.maxRetries)) return [3 /*break*/, 4];
202
+ return [5 /*yield**/, _loop_1(attempt)];
203
+ case 2:
204
+ state_1 = _a.sent();
205
+ if (typeof state_1 === "object")
206
+ return [2 /*return*/, state_1.value];
207
+ if (state_1 === "break")
208
+ return [3 /*break*/, 4];
209
+ _a.label = 3;
210
+ case 3:
211
+ attempt++;
212
+ return [3 /*break*/, 1];
213
+ case 4:
214
+ // All retries exhausted, throw the last error
215
+ throw lastError;
216
+ }
217
+ });
218
+ });
219
+ }
220
+ /**
221
+ * Create a fetch function with custom retry configuration
222
+ * Useful for creating a configured fetch instance
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * import { createFetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
227
+ *
228
+ * // Create configured fetch
229
+ * const customFetch = createFetchWithRetry({
230
+ * maxRetries: 5,
231
+ * timeoutMs: 15000,
232
+ * });
233
+ *
234
+ * // Use it like regular fetch
235
+ * const response = await customFetch('https://api.example.com/products');
236
+ * ```
237
+ */
238
+ function createFetchWithRetry(retryConfig) {
239
+ return function (url, init) { return fetchWithRetry(url, init, retryConfig); };
240
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Fetch Utilities - Retry logic, timeouts, and error handling
4
+ *
5
+ * This module provides enhanced fetch capabilities with:
6
+ * - Automatic retries with exponential backoff
7
+ * - Configurable timeouts
8
+ * - Better error handling for network failures
9
+ */
10
+
11
+ import { createLogger } from './logger';
12
+
13
+ const logger = createLogger('FetchUtils');
14
+
15
+ /**
16
+ * Configuration for fetch retry behavior
17
+ */
18
+ export interface RetryConfig {
19
+ /**
20
+ * Maximum number of retry attempts (default: 3)
21
+ */
22
+ maxRetries?: number;
23
+
24
+ /**
25
+ * Initial delay in milliseconds before first retry (default: 300ms)
26
+ */
27
+ initialDelayMs?: number;
28
+
29
+ /**
30
+ * Multiplier for exponential backoff (default: 2)
31
+ */
32
+ backoffMultiplier?: number;
33
+
34
+ /**
35
+ * Maximum delay between retries in milliseconds (default: 5000ms)
36
+ */
37
+ maxDelayMs?: number;
38
+
39
+ /**
40
+ * Timeout for each fetch attempt in milliseconds (default: 10000ms - 10 seconds)
41
+ */
42
+ timeoutMs?: number;
43
+
44
+ /**
45
+ * HTTP status codes that should trigger a retry (default: [408, 429, 500, 502, 503, 504])
46
+ */
47
+ retryableStatusCodes?: number[];
48
+ }
49
+
50
+ const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
51
+ maxRetries: 3,
52
+ initialDelayMs: 300,
53
+ backoffMultiplier: 2,
54
+ maxDelayMs: 5000,
55
+ timeoutMs: 10000, // 10 seconds
56
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504],
57
+ };
58
+
59
+ /**
60
+ * Sleep utility for delays between retries
61
+ */
62
+ function sleep(ms: number): Promise<void> {
63
+ return new Promise(resolve => setTimeout(resolve, ms));
64
+ }
65
+
66
+ /**
67
+ * Calculate delay for exponential backoff
68
+ */
69
+ function calculateDelay(
70
+ attempt: number,
71
+ config: Required<RetryConfig>
72
+ ): number {
73
+ const delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
74
+ return Math.min(delay, config.maxDelayMs);
75
+ }
76
+
77
+ /**
78
+ * Check if error is retryable
79
+ */
80
+ function isRetryableError(error: any, config: Required<RetryConfig>): boolean {
81
+ // Network errors (fetch failed, connection refused, etc.)
82
+ if (error.name === 'TypeError' || error.message?.includes('fetch failed')) {
83
+ return true;
84
+ }
85
+
86
+ // Timeout errors
87
+ if (error.name === 'AbortError' || error.message?.includes('timeout')) {
88
+ return true;
89
+ }
90
+
91
+ // HTTP status codes
92
+ if (error.status && config.retryableStatusCodes.includes(error.status)) {
93
+ return true;
94
+ }
95
+
96
+ return false;
97
+ }
98
+
99
+ /**
100
+ * Enhanced fetch with retry logic and timeout
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import { fetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
105
+ *
106
+ * // Basic usage with defaults (3 retries, 10s timeout)
107
+ * const response = await fetchWithRetry('https://api.example.com/products');
108
+ *
109
+ * // Custom configuration
110
+ * const response = await fetchWithRetry(
111
+ * 'https://api.example.com/products',
112
+ * {
113
+ * method: 'POST',
114
+ * body: JSON.stringify({ name: 'Product' }),
115
+ * },
116
+ * {
117
+ * maxRetries: 5,
118
+ * timeoutMs: 15000, // 15 seconds
119
+ * initialDelayMs: 500,
120
+ * }
121
+ * );
122
+ * ```
123
+ */
124
+ export async function fetchWithRetry(
125
+ url: string,
126
+ init?: RequestInit,
127
+ retryConfig?: RetryConfig
128
+ ): Promise<Response> {
129
+ const config = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
130
+ let lastError: any;
131
+
132
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
133
+ try {
134
+ // Create AbortController for timeout
135
+ const controller = new AbortController();
136
+ const timeoutId = setTimeout(() => controller.abort(), config.timeoutMs);
137
+
138
+ const fetchOptions: RequestInit = {
139
+ ...init,
140
+ signal: controller.signal,
141
+ };
142
+
143
+ logger.debug('Fetch attempt', {
144
+ url,
145
+ attempt: attempt + 1,
146
+ maxAttempts: config.maxRetries + 1,
147
+ timeout: config.timeoutMs,
148
+ });
149
+
150
+ // Execute fetch
151
+ const response = await fetch(url, fetchOptions);
152
+
153
+ // Clear timeout
154
+ clearTimeout(timeoutId);
155
+
156
+ // Check if status code is retryable
157
+ if (!response.ok && config.retryableStatusCodes.includes(response.status)) {
158
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
159
+ (error as any).status = response.status;
160
+ throw error;
161
+ }
162
+
163
+ // Success!
164
+ logger.debug('Fetch successful', { url, attempt: attempt + 1 });
165
+ return response;
166
+
167
+ } catch (error: any) {
168
+ lastError = error;
169
+
170
+ // Clear timeout if it exists
171
+ if (error.name === 'AbortError') {
172
+ lastError = new Error(`Request timeout after ${config.timeoutMs}ms`);
173
+ lastError.name = 'TimeoutError';
174
+ }
175
+
176
+ // Check if we should retry
177
+ const shouldRetry = attempt < config.maxRetries && isRetryableError(error, config);
178
+
179
+ if (shouldRetry) {
180
+ const delay = calculateDelay(attempt, config);
181
+ logger.warn('Fetch failed, retrying...', {
182
+ url,
183
+ attempt: attempt + 1,
184
+ maxAttempts: config.maxRetries + 1,
185
+ nextRetryIn: `${delay}ms`,
186
+ error: error.message,
187
+ });
188
+
189
+ await sleep(delay);
190
+ } else {
191
+ logger.error('Fetch failed after all retries', {
192
+ url,
193
+ totalAttempts: attempt + 1,
194
+ error: error.message,
195
+ });
196
+ break;
197
+ }
198
+ }
199
+ }
200
+
201
+ // All retries exhausted, throw the last error
202
+ throw lastError;
203
+ }
204
+
205
+ /**
206
+ * Create a fetch function with custom retry configuration
207
+ * Useful for creating a configured fetch instance
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * import { createFetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
212
+ *
213
+ * // Create configured fetch
214
+ * const customFetch = createFetchWithRetry({
215
+ * maxRetries: 5,
216
+ * timeoutMs: 15000,
217
+ * });
218
+ *
219
+ * // Use it like regular fetch
220
+ * const response = await customFetch('https://api.example.com/products');
221
+ * ```
222
+ */
223
+ export function createFetchWithRetry(
224
+ retryConfig?: RetryConfig
225
+ ): (url: string, init?: RequestInit) => Promise<Response> {
226
+ return (url: string, init?: RequestInit) => fetchWithRetry(url, init, retryConfig);
227
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Core utilities exports
4
+ */
5
+
6
+ export * from './errors';
7
+ export * from './logger';
8
+ export * from './fetch-utils';
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * @thorprovider/adapters v2.0
4
+ * Core logger utility
5
+ */
6
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
7
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
8
+ if (ar || !(i in from)) {
9
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
10
+ ar[i] = from[i];
11
+ }
12
+ }
13
+ return to.concat(ar || Array.prototype.slice.call(from));
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.createLogger = createLogger;
17
+ /**
18
+ * Create a logger instance
19
+ */
20
+ function createLogger(namespace, enabled) {
21
+ if (enabled === void 0) { enabled = false; }
22
+ var log = function (level, message) {
23
+ var args = [];
24
+ for (var _i = 2; _i < arguments.length; _i++) {
25
+ args[_i - 2] = arguments[_i];
26
+ }
27
+ if (!enabled && level !== 'error')
28
+ return;
29
+ var timestamp = new Date().toISOString();
30
+ var prefix = "[".concat(timestamp, "] [@thorprovider/adapters:").concat(namespace, "] ").concat(level.toUpperCase(), ":");
31
+ console[level].apply(console, __spreadArray([prefix, message], args, false));
32
+ };
33
+ return {
34
+ debug: function (message) {
35
+ var args = [];
36
+ for (var _i = 1; _i < arguments.length; _i++) {
37
+ args[_i - 1] = arguments[_i];
38
+ }
39
+ return log.apply(void 0, __spreadArray(['debug', message], args, false));
40
+ },
41
+ info: function (message) {
42
+ var args = [];
43
+ for (var _i = 1; _i < arguments.length; _i++) {
44
+ args[_i - 1] = arguments[_i];
45
+ }
46
+ return log.apply(void 0, __spreadArray(['info', message], args, false));
47
+ },
48
+ warn: function (message) {
49
+ var args = [];
50
+ for (var _i = 1; _i < arguments.length; _i++) {
51
+ args[_i - 1] = arguments[_i];
52
+ }
53
+ return log.apply(void 0, __spreadArray(['warn', message], args, false));
54
+ },
55
+ error: function (message) {
56
+ var args = [];
57
+ for (var _i = 1; _i < arguments.length; _i++) {
58
+ args[_i - 1] = arguments[_i];
59
+ }
60
+ return log.apply(void 0, __spreadArray(['error', message], args, false));
61
+ },
62
+ };
63
+ }