@thorprovider/adapters 4.2.0 → 4.2.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 (48) hide show
  1. package/.turbo/turbo-build.log +18 -18
  2. package/.turbo/turbo-test.log +1 -0
  3. package/.turbo/turbo-type-check.log +0 -0
  4. package/AGENTS.md +1 -1
  5. package/CHANGELOG.md +12 -0
  6. package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
  7. package/README.md +22 -3
  8. package/dist/.tsbuildinfo +1 -1
  9. package/dist/index.d.mts +140 -2
  10. package/dist/index.d.ts +140 -2
  11. package/dist/index.js +1075 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +1070 -0
  14. package/dist/index.mjs.map +1 -1
  15. package/docs/RETRY_SOLUTION_SUMMARY.md +26 -11
  16. package/docs/authentication.md +89 -62
  17. package/docs/capabilities-system.md +100 -38
  18. package/docs/category-customization.md +37 -29
  19. package/docs/fetch-reliability-solution.md +127 -278
  20. package/docs/getting-started.md +43 -29
  21. package/docs/profile-management.md +68 -38
  22. package/docs/providers-overview.md +14 -0
  23. package/docs/services-overview.md +20 -2
  24. package/docs/update-documentation.md +10 -8
  25. package/package.json +11 -6
  26. package/publish.log +93 -0
  27. package/quick-reference.md +1 -0
  28. package/src/factory/index.ts +4 -0
  29. package/src/index.ts +7 -0
  30. package/src/providers/index.ts +3 -0
  31. package/src/providers/medusa/admin.test.ts +404 -0
  32. package/src/providers/medusa/admin.ts +442 -0
  33. package/src/providers/medusa/auth.test.ts +95 -0
  34. package/src/providers/medusa/index.test.ts +392 -0
  35. package/src/providers/medusa/live-smoke.test.ts +412 -0
  36. package/src/providers/stelorder/client.test-helpers.ts +3 -0
  37. package/src/providers/stelorder/client.test.ts +58 -0
  38. package/src/providers/stelorder/client.ts +326 -0
  39. package/src/providers/stelorder/index.test.ts +48 -0
  40. package/src/providers/stelorder/index.ts +685 -0
  41. package/src/providers/stelorder/live-smoke.test.ts +54 -0
  42. package/src/providers/stelorder/transforms.test.ts +103 -0
  43. package/src/providers/stelorder/transforms.ts +631 -0
  44. package/src/resilience/README.md +70 -48
  45. package/src/types/capabilities.types.ts +51 -0
  46. package/vitest.config.ts +13 -0
  47. package/vitest.live.config.ts +18 -0
  48. package/vitest.setup.ts +6 -0
@@ -1,3 +1,14 @@
1
+ ---
2
+ title: Resilience Patterns
3
+ purpose: Documents the Circuit Breaker and retry patterns for robust backend integration
4
+ type: reference
5
+ tags: [resilience, circuit-breaker, retry, fetch, patterns]
6
+ related:
7
+ - ../../docs/fetch-reliability-solution.md
8
+ - ../../docs/RETRY_SOLUTION_SUMMARY.md
9
+ updated: 2026-05-03
10
+ ---
11
+
1
12
  # Resilience Patterns
2
13
 
3
14
  **Framework-agnostic resilience patterns for robust backend integration.**
@@ -10,10 +21,17 @@ This module provides enterprise-grade resilience patterns for handling backend f
10
21
 
11
22
  ---
12
23
 
13
- ## 📦 Export
24
+ ## 📦 Exports
14
25
 
15
26
  ```typescript
16
- import { CircuitBreaker, backendCircuitBreaker } from '@thorprovider/adapters/resilience';
27
+ import {
28
+ CircuitBreaker,
29
+ backendCircuitBreaker,
30
+ CircuitBreakerOpenError,
31
+ type CircuitState,
32
+ type CircuitBreakerConfig,
33
+ type CircuitBreakerStats,
34
+ } from '@thorprovider/adapters';
17
35
  ```
18
36
 
19
37
  ---
@@ -53,7 +71,7 @@ The Circuit Breaker pattern prevents cascading failures when the backend is unav
53
71
  ### Basic Usage
54
72
 
55
73
  ```typescript
56
- import { CircuitBreaker } from '@thorprovider/adapters/resilience';
74
+ import { CircuitBreaker } from '@thorprovider/adapters';
57
75
 
58
76
  const breaker = new CircuitBreaker({
59
77
  failureThreshold: 5, // Open after 5 failures
@@ -63,7 +81,7 @@ const breaker = new CircuitBreaker({
63
81
 
64
82
  // With fallback
65
83
  const data = await breaker.execute(
66
- () => fetch('/api/data'),
84
+ () => fetch('/api/data').then(r => r.json()),
67
85
  { default: 'value' } // Fallback if circuit is open
68
86
  );
69
87
 
@@ -81,11 +99,11 @@ try {
81
99
  interface CircuitBreakerConfig {
82
100
  /** Number of failures before opening circuit */
83
101
  failureThreshold: number;
84
-
102
+
85
103
  /** Time in ms to wait before attempting recovery */
86
104
  resetTimeout: number;
87
-
88
- /** Name for logging/监控 */
105
+
106
+ /** Name for logging/monitoring */
89
107
  name?: string;
90
108
  }
91
109
  ```
@@ -103,7 +121,7 @@ console.log({
103
121
  failureCount: stats.failureCount,
104
122
  totalRequests: stats.totalRequests,
105
123
  totalFailures: stats.totalFailures,
106
- totalSuccesses: stats.totalSuccesses
124
+ totalSuccesses: stats.totalSuccesses,
107
125
  });
108
126
 
109
127
  // Manual reset (force recovery)
@@ -117,35 +135,34 @@ breaker.manualReset();
117
135
  A pre-configured global instance for backend API calls:
118
136
 
119
137
  ```typescript
120
- import { backendCircuitBreaker } from '@thorprovider/adapters/resilience';
138
+ import { backendCircuitBreaker } from '@thorprovider/adapters';
121
139
 
122
140
  // Use in adapters
123
141
  async function getProducts() {
124
142
  return backendCircuitBreaker.execute(
125
- () => fetch('/api/products'),
143
+ () => commerce.getProducts(),
126
144
  [] // Empty array fallback
127
145
  );
128
146
  }
129
147
  ```
130
148
 
131
- **Configuration:**
149
+ **Default configuration:**
132
150
  - `failureThreshold`: 5 failures
133
151
  - `resetTimeout`: 60,000ms (1 minute)
134
- - `name`: 'backend-api'
152
+ - `name`: `'backend-api'`
135
153
 
136
154
  ---
137
155
 
138
156
  ## 🎨 Usage Patterns
139
157
 
140
- ### Pattern 1: L2 Adapter with Graceful Degradation
158
+ ### Pattern 1: Adapter with Graceful Degradation
141
159
 
142
160
  ```typescript
143
- // packages/adapters/src/medusa/product-adapter.ts
144
- import { backendCircuitBreaker } from '@thorprovider/adapters/resilience';
161
+ import { backendCircuitBreaker } from '@thorprovider/adapters';
145
162
 
146
163
  export async function getProducts(options: GetProductsOptions) {
147
164
  return backendCircuitBreaker.execute(
148
- () => medusaClient.products.list(options),
165
+ () => commerce.getProducts(options),
149
166
  [] // Return empty array if backend is down
150
167
  );
151
168
  }
@@ -156,23 +173,23 @@ export async function getProducts(options: GetProductsOptions) {
156
173
  - ✅ User sees "No products" instead of error page
157
174
  - ✅ Backend gets time to recover
158
175
 
159
- ### Pattern 2: L5 Server Component with Fallback UI
176
+ ### Pattern 2: Server Component with Fallback UI
160
177
 
161
178
  ```typescript
162
- // packages/core/app/products/page.tsx
163
- import { backendCircuitBreaker } from '@thorprovider/adapters/resilience';
164
- import { getProducts } from '@thorprovider/adapters/medusa';
179
+ // app/products/page.tsx
180
+ import { backendCircuitBreaker } from '@thorprovider/adapters';
181
+ import { commerce } from '@/lib/commerce';
165
182
 
166
183
  export default async function ProductsPage() {
167
184
  const products = await backendCircuitBreaker.execute(
168
- () => getProducts(),
169
- [] // Fallback to empty array
185
+ () => commerce.getProducts(),
186
+ []
170
187
  );
171
-
188
+
172
189
  if (products.length === 0) {
173
- return <EmptyState message="Products temporarily unavailable" />;
190
+ return <p>Products temporarily unavailable</p>;
174
191
  }
175
-
192
+
176
193
  return <ProductsGrid products={products} />;
177
194
  }
178
195
  ```
@@ -181,14 +198,13 @@ export default async function ProductsPage() {
181
198
 
182
199
  ```typescript
183
200
  // Critical operations should fail explicitly
184
- async function completeCheckout(data: CheckoutData) {
201
+ async function completeCheckout(cartId: string) {
185
202
  try {
186
203
  return await backendCircuitBreaker.execute(
187
- () => medusaClient.carts.complete(data)
188
- // NO fallback - we want this to fail if backend is down
204
+ () => commerce.completeCheckout(cartId)
205
+ // NO fallback — we want this to fail if backend is down
189
206
  );
190
207
  } catch (error) {
191
- // Show error message to user
192
208
  throw new Error('Checkout unavailable. Please try again later.');
193
209
  }
194
210
  }
@@ -198,10 +214,9 @@ async function completeCheckout(data: CheckoutData) {
198
214
 
199
215
  ## 🧪 Testing
200
216
 
201
- ### Unit Test Example
202
-
203
217
  ```typescript
204
- import { CircuitBreaker } from '@thorprovider/adapters/resilience';
218
+ import { describe, it, expect, vi } from 'vitest';
219
+ import { CircuitBreaker } from '@thorprovider/adapters';
205
220
 
206
221
  describe('CircuitBreaker', () => {
207
222
  it('opens after threshold failures', async () => {
@@ -209,28 +224,28 @@ describe('CircuitBreaker', () => {
209
224
  failureThreshold: 3,
210
225
  resetTimeout: 1000,
211
226
  });
212
-
213
- const mockFn = jest.fn().mockRejectedValue(new Error('fail'));
214
-
227
+
228
+ const mockFn = vi.fn().mockRejectedValue(new Error('fail'));
229
+
215
230
  // 3 failures
216
231
  await expect(breaker.execute(mockFn)).rejects.toThrow();
217
232
  await expect(breaker.execute(mockFn)).rejects.toThrow();
218
233
  await expect(breaker.execute(mockFn)).rejects.toThrow();
219
-
234
+
220
235
  expect(breaker.state).toBe('OPEN');
221
236
  });
222
-
237
+
223
238
  it('uses fallback when circuit is open', async () => {
224
239
  const breaker = new CircuitBreaker({
225
240
  failureThreshold: 1,
226
241
  resetTimeout: 1000,
227
242
  });
228
-
229
- const mockFn = jest.fn().mockRejectedValue(new Error('fail'));
230
-
243
+
244
+ const mockFn = vi.fn().mockRejectedValue(new Error('fail'));
245
+
231
246
  // Open circuit
232
247
  await breaker.execute(mockFn, 'fallback').catch(() => {});
233
-
248
+
234
249
  // Should return fallback immediately
235
250
  const result = await breaker.execute(mockFn, 'fallback');
236
251
  expect(result).toBe('fallback');
@@ -239,6 +254,12 @@ describe('CircuitBreaker', () => {
239
254
  });
240
255
  ```
241
256
 
257
+ Run tests:
258
+
259
+ ```bash
260
+ yarn workspace @thorprovider/adapters test
261
+ ```
262
+
242
263
  ---
243
264
 
244
265
  ## 📐 Architecture Decisions
@@ -271,29 +292,30 @@ describe('CircuitBreaker', () => {
271
292
 
272
293
  - Don't use for client-side only operations
273
294
  - Don't ignore OPEN state errors silently
274
- - Don't set failureThreshold too low (causes flapping)
275
- - Don't set resetTimeout too high (slow recovery)
295
+ - Don't set `failureThreshold` too low (causes flapping)
296
+ - Don't set `resetTimeout` too high (slow recovery)
276
297
 
277
298
  ---
278
299
 
279
300
  ## 🔮 Future Enhancements
280
301
 
281
- - [ ] **Retry Pattern**: Exponential backoff for transient failures
282
- - [ ] **Bulkhead Pattern**: Isolate resource pools
302
+ - [ ] **Bulkhead Pattern**: Isolate resource pools per service
283
303
  - [ ] **Rate Limiting**: Prevent API abuse
284
304
  - [ ] **Metrics Export**: Prometheus/Datadog integration
285
305
  - [ ] **Health Check Integration**: Auto-open circuit on health check fail
286
306
 
307
+ > ✅ **Retry Pattern** with exponential backoff is already implemented in `src/core/fetch-utils.ts`.
308
+
287
309
  ---
288
310
 
289
311
  ## 📚 References
290
312
 
291
- - [Martin Fowler - Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
292
- - [Microsoft - Resilience Patterns](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
313
+ - [Martin Fowler — Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
314
+ - [Microsoft — Resilience Patterns](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
293
315
  - [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki)
294
316
 
295
317
  ---
296
318
 
297
- **Last Updated**: March 24, 2026
319
+ **Last Updated**: 2026-05-03
298
320
  **Status**: ✅ Active
299
321
  **Layer**: L2 (Adapters)
@@ -99,6 +99,57 @@ export const MEDUSA_CAPABILITIES: Readonly<BackendCapabilities> = {
99
99
  loyaltyProgram: false, // Requires custom implementation
100
100
  };
101
101
 
102
+ /**
103
+ * Stel Order backend capabilities
104
+ *
105
+ * ERP/document API with catalog, CRM, inventory visibility, and sales documents,
106
+ * but without cart, checkout, customer auth, or storefront-native payment flows.
107
+ */
108
+ export const STELORDER_CAPABILITIES: Readonly<BackendCapabilities> = {
109
+ // Profile Management
110
+ updateProfile: false,
111
+ updateEmail: false,
112
+ changePasswordDirect: false,
113
+ changePasswordViaEmail: false,
114
+ deleteAccount: false,
115
+
116
+ // Avatar
117
+ uploadAvatar: false,
118
+ avatarViaMetadata: false,
119
+
120
+ // Custom Attributes
121
+ customAttributes: false,
122
+
123
+ // Orders
124
+ fetchOrders: true,
125
+ cancelOrder: false,
126
+
127
+ // Addresses
128
+ manageAddresses: true,
129
+ defaultAddress: false,
130
+
131
+ // Cart/Checkout
132
+ applyDiscounts: false,
133
+ shippingCalculation: false,
134
+ guestCheckout: false,
135
+ savedPaymentMethods: false,
136
+ listPaymentProviders: true,
137
+ regionSpecificPayments: false,
138
+
139
+ // Wishlist
140
+ wishlist: false,
141
+
142
+ // Search
143
+ advancedSearch: false,
144
+ facetedFilters: false,
145
+
146
+ // Reviews
147
+ productReviews: false,
148
+
149
+ // Loyalty
150
+ loyaltyProgram: false,
151
+ };
152
+
102
153
  /**
103
154
  * Shopify backend capabilities
104
155
  *
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ globals: true,
7
+ setupFiles: ['./vitest.setup.ts'],
8
+ include: ['src/**/*.test.ts'],
9
+ restoreMocks: true,
10
+ clearMocks: true,
11
+ mockReset: true,
12
+ },
13
+ })
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ /**
4
+ * Vitest config for live smoke tests — no MSW setup (real HTTP).
5
+ * Forces TypeScript resolution over stale .js files in src/.
6
+ */
7
+ export default defineConfig({
8
+ resolve: {
9
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
10
+ },
11
+ test: {
12
+ environment: 'node',
13
+ globals: true,
14
+ include: ['src/**/live-smoke.test.ts'],
15
+ testTimeout: 45000,
16
+ retry: 1, // Retry once on transient network failures
17
+ },
18
+ })
@@ -0,0 +1,6 @@
1
+ import { afterAll, afterEach, beforeAll } from 'vitest'
2
+ import { server } from './src/providers/stelorder/client.test-helpers'
3
+
4
+ beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
5
+ afterEach(() => server.resetHandlers())
6
+ afterAll(() => server.close())