@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,55 @@
1
+ # Resumen Ejecutivo - Solución a Errores de Fetch
2
+
3
+ ## ❌ Problema
4
+ Errores frecuentes `Failed to fetch products: fetch failed` en el V0 starter al cargar productos desde el backend de Medusa.
5
+
6
+ ## ✅ Solución Implementada
7
+
8
+ ### 1. Nuevo Módulo de Retry Logic (`fetch-utils.ts`)
9
+ - **Ubicación**: `packages/adapters/src/core/fetch-utils.ts`
10
+ - **Funcionalidad**:
11
+ - Reintentos automáticos con exponential backoff
12
+ - Timeout configurable (10s por defecto)
13
+ - Detección inteligente de errores retryables
14
+ - Logging detallado
15
+
16
+ ### 2. Integración en MedusaProvider
17
+ - **Ubicación**: `packages/adapters/src/providers/medusa/index.ts`
18
+ - **Cambios**:
19
+ - Nueva propiedad `retry?: RetryConfig` en `MedusaProviderConfig`
20
+ - Método privado `withRetry()` que envuelve todas las llamadas al SDK
21
+ - Métodos `getProduct()` y `getProducts()` actualizados para usar retry logic
22
+
23
+ ### 3. Configuración por Defecto en V0 Starter
24
+ - **Ubicación**: `packages/core/lib/commerce.ts`
25
+ - **Configuración**:
26
+ ```typescript
27
+ retry: {
28
+ maxRetries: 3, // 4 intentos totales
29
+ timeoutMs: 10000, // 10 segundos por request
30
+ initialDelayMs: 300, // Delay inicial 300ms
31
+ backoffMultiplier: 2, // Exponential backoff (2x)
32
+ maxDelayMs: 5000, // Cap en 5 segundos
33
+ }
34
+ ```
35
+
36
+ ## 📊 Impacto Esperado
37
+ - **90-98% reducción** en errores visibles al usuario
38
+ - Resiliencia ante problemas de red transitorios
39
+ - Mejor experiencia de usuario sin interrupciones
40
+
41
+ ## 🧪 Estado
42
+ - ✅ Código compila sin errores TypeScript
43
+ - ✅ Build de adapters exitoso
44
+ - ✅ Build de elements exitoso
45
+ - ✅ Documentación completa creada
46
+ - 🔄 Pendiente: Testing en dev/staging
47
+
48
+ ## 📝 Documentación Completa
49
+ Ver [fetch-reliability-solution.md](./fetch-reliability-solution.md) para análisis detallado de causas, soluciones, y configuraciones avanzadas.
50
+
51
+ ## 🚀 Next Steps
52
+ 1. Probar en desarrollo: `yarn dev:showcase` o `yarn workspace @thorprovider/core dev`
53
+ 2. Verificar logs en consola mostrando reintentos
54
+ 3. Confirmar reducción de errores en producción
55
+ 4. (Opcional) Ajustar configuración de retry según métricas reales
@@ -0,0 +1,165 @@
1
+ # Authentication
2
+
3
+ ---
4
+ type: how-to
5
+ tags: [auth, security, login, jwt, session, registration]
6
+ related:
7
+ - profile-management.md
8
+ - services-overview.md
9
+ - getting-started.md
10
+ updated: 2026-02-28
11
+ ---
12
+
13
+ Complete guide to setting up and using authentication with Thor adapters.
14
+
15
+ ## Prerequisites
16
+
17
+ - [Thor Adapters installed](getting-started.md#installation)
18
+ - A Medusa.js v2 backend configured with authentication providers
19
+
20
+ ## Quick Example
21
+
22
+ ```typescript
23
+ import { commerce } from '@/lib/commerce';
24
+
25
+ // Simple login
26
+ const { customer } = await commerce.auth.login({
27
+ email: 'user@example.com',
28
+ password: 'SecurePassword123!',
29
+ });
30
+
31
+ console.log(`Welcome back, ${customer.first_name}!`);
32
+ ```
33
+
34
+ ## Step-by-Step Instructions
35
+
36
+ ### Step 1: Configure Auth Method
37
+
38
+ Choose between JWT (SPAs) or Session (SSR) in your provider configuration.
39
+
40
+ ```typescript
41
+ // lib/commerce.ts
42
+ export const commerce = createProvider({
43
+ provider: 'medusa',
44
+ config: {
45
+ // ... basic config
46
+ auth: {
47
+ method: 'jwt', // Recommended for SPAs
48
+ storage: 'localStorage', // Where to persist tokens
49
+ },
50
+ },
51
+ });
52
+ ```
53
+
54
+ ### Step 2: Handle Registration
55
+
56
+ Capture user data and create a new customer account.
57
+
58
+ ```typescript
59
+ const { customer } = await commerce.auth.register({
60
+ email: 'new@example.com',
61
+ password: 'Password123!',
62
+ first_name: 'Jane',
63
+ last_name: 'Doe',
64
+ });
65
+ ```
66
+
67
+ ### Step 3: Implement Login Flow
68
+
69
+ Create a login handler in your UI components.
70
+
71
+ ```typescript
72
+ async function handleLogin(credentials) {
73
+ try {
74
+ await commerce.auth.login(credentials);
75
+ // Redirect on success
76
+ } catch (error) {
77
+ // Handle AuthError
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Step 4: Check Auth State
83
+
84
+ Protect routes or update UI based on authentication status.
85
+
86
+ ```typescript
87
+ const isAuth = await commerce.auth.isAuthenticated();
88
+ const customer = await commerce.auth.getCurrentCustomer();
89
+ ```
90
+
91
+ ## ✅ DO's
92
+
93
+ 1. **Use JWT for Headless SPAs:** It's more flexible and works better across different origins.
94
+ 2. **Use Session for SSR:** If you use Next.js Server Components, Session cookies are more secure.
95
+ 3. **Handle AuthErrors specifically:** Use `instanceof AuthError` to show user-friendly messages.
96
+ 4. **Enable Auto-refresh:** Keep `autoRefresh: true` (default) to ensure seamless user sessions.
97
+ 5. **Clear local state on logout:** Always call `commerce.auth.logout()` to clear stored tokens.
98
+
99
+ ## ❌ DONT's
100
+
101
+ 1. **Don't store sensitive data in customer object:** Only use public profile fields.
102
+ 2. **Don't handle JWT tokens manually:** Let the adapter manage storage and refresh headers.
103
+ 3. **Don't use Secret Keys in the browser:** Authentication should only use Publishable Keys.
104
+ 4. **Don't ignore the `isAuthenticated` check:** Verify auth status before performing protected operations.
105
+ 5. **Don't hardcode login URLs:** The adapter knows where to send requests based on the provider.
106
+
107
+ ## Common Patterns
108
+
109
+ ### Protected Route Hook (React)
110
+ ```typescript
111
+ function useRequireAuth() {
112
+ const { customer, loading } = useAuth(); // custom hook
113
+ const router = useRouter();
114
+
115
+ useEffect(() => {
116
+ if (!loading && !customer) router.push('/login');
117
+ }, [customer, loading]);
118
+
119
+ return { customer, loading };
120
+ }
121
+ ```
122
+
123
+ ### Server-Side Auth Check (Next.js)
124
+ ```typescript
125
+ export async function getServerSideProps() {
126
+ const customer = await commerce.auth.getCurrentCustomer();
127
+ if (!customer) return { redirect: { destination: '/login' } };
128
+ return { props: { customer } };
129
+ }
130
+ ```
131
+
132
+ ## Common Errors & Solutions
133
+
134
+ ### Error: `AUTH_FAILED`
135
+ **Cause:** Incorrect email or password.
136
+ **Solution:** Show "Invalid credentials" message to the user.
137
+
138
+ ### Error: `TOKEN_EXPIRED`
139
+ **Cause:** Refresh token expired or user was logged out elsewhere.
140
+ **Solution:** Redirect to login page to re-authenticate.
141
+
142
+ ### Error: `USER_NOT_FOUND`
143
+ **Cause:** Attempting to login with a non-existent email.
144
+ **Solution:** Prompt user to register if they don't have an account.
145
+
146
+ ## Common Pitfalls
147
+
148
+ > ⚠️ **Pitfall 1: CORS issues with Session cookies**
149
+ > Session-based auth requires matching domains or properly configured `cross-site` cookies in the backend.
150
+
151
+ > ⚠️ **Pitfall 2: LocalStorage availability in SSR**
152
+ > Ensure token management logic checks for `window` availability if using `localStorage`.
153
+
154
+ ## Troubleshooting
155
+
156
+ | Issue | Solution |
157
+ |-------|----------|
158
+ | Token not sent in headers | Check if `auth.method` matches backend expectation |
159
+ | Login works but profile fails | Verify `storage` type is correctly configured and working |
160
+ | Infinite login loop | Check cookie path and domain settings on the backend |
161
+
162
+ ## Next Steps
163
+
164
+ - [Profile Management](profile-management.md)
165
+ - [Services Overview](services-overview.md)
@@ -0,0 +1,143 @@
1
+ # Capabilities System
2
+
3
+ ---
4
+ type: reference
5
+ tags: [architecture, capabilities, backend, detection]
6
+ related:
7
+ - providers-overview.md
8
+ - services-overview.md
9
+ updated: 2026-02-28
10
+ ---
11
+
12
+ The capabilities system exposes which operations each backend provider supports, enabling adaptive UIs across platforms.
13
+
14
+ ## Quick Reference
15
+
16
+ ```typescript
17
+ import { commerce } from '@/lib/commerce';
18
+
19
+ // Check any capability
20
+ if (commerce.capabilities.updateEmail) {
21
+ // Safe to call
22
+ await commerce.auth.updateCustomer({ email: 'new@email.com' });
23
+ }
24
+ ```
25
+
26
+ ## Available Capabilities
27
+
28
+ ### Profile Operations
29
+
30
+ | Capability | Type | Description |
31
+ |------------|------|-------------|
32
+ | `updateProfile` | `boolean` | Can update name, phone, etc. |
33
+ | `updateEmail` | `boolean` | Can change the primary email address. |
34
+ | `changePasswordDirect` | `boolean` | Supports `password` field in `updateCustomer`. |
35
+ | `changePasswordViaEmail` | `boolean` | Supports `requestPasswordReset` method. |
36
+ | `deleteAccount` | `boolean` | Supports `deleteAccount` method. |
37
+ | `uploadAvatar` | `boolean` | Supports native file upload for avatars. |
38
+
39
+ ### Cart Operations
40
+
41
+ | Capability | Type | Description |
42
+ |------------|------|-------------|
43
+ | `cartPromoCode` | `boolean` | Can apply discount/promo codes to cart. |
44
+ | `cartGiftCards` | `boolean` | Supports gift card application. |
45
+ | `cartNotes` | `boolean` | Supports order/cart notes. |
46
+ | `cartCustomAttributes` | `boolean` | Supports metadata/attributes at cart level. |
47
+
48
+ ### Product & Discovery
49
+
50
+ | Capability | Type | Description |
51
+ |------------|------|-------------|
52
+ | `productVariants` | `boolean` | Backend supports multiple variants per product. |
53
+ | `productCategories` | `boolean` | Supports nested categories or collections. |
54
+ | `productInventory` | `boolean` | Real-time inventory tracking available. |
55
+ | `productDigital` | `boolean` | Supports digital/downloadable products. |
56
+ | `productReviews` | `boolean` | Native review system support. |
57
+
58
+ ## Type Definitions
59
+
60
+ ```typescript
61
+ export interface BackendCapabilities {
62
+ // Profile
63
+ updateProfile: boolean;
64
+ updateEmail: boolean;
65
+ changePasswordDirect: boolean;
66
+ changePasswordViaEmail: boolean;
67
+ deleteAccount: boolean;
68
+ uploadAvatar: boolean;
69
+
70
+ // Avatar Extensions
71
+ avatarViaMetadata: boolean;
72
+
73
+ // Cart
74
+ cartPromoCode: boolean;
75
+ cartGiftCards: boolean;
76
+ cartNotes: boolean;
77
+ cartCustomAttributes: boolean;
78
+
79
+ // Products
80
+ productVariants: boolean;
81
+ productCategories: boolean;
82
+ productInventory: boolean;
83
+ productDigital: boolean;
84
+ productReviews: boolean;
85
+
86
+ // Auth Integration
87
+ authJWT: boolean;
88
+ authSession: boolean;
89
+ authOAuth: boolean;
90
+ authPasswordless: boolean;
91
+ }
92
+ ```
93
+
94
+ ## Examples
95
+
96
+ ### Basic: Conditional UI
97
+ ```tsx
98
+ const { canDelete } = useCapabilities();
99
+
100
+ return (
101
+ <Button disabled={!canDelete} color="red">
102
+ Delete Account
103
+ </Button>
104
+ );
105
+ ```
106
+
107
+ ### Advanced: Multi-provider Fallback
108
+ ```typescript
109
+ async function handleAvatarUpdate(file: File) {
110
+ if (caps.uploadAvatar) {
111
+ return await commerce.auth.uploadAvatar(file);
112
+ }
113
+
114
+ if (caps.avatarViaMetadata) {
115
+ const url = await externalUpload(file);
116
+ return await commerce.auth.updateCustomer({
117
+ metadata: { avatar_url: url }
118
+ });
119
+ }
120
+
121
+ throw new Error('Avatar updates not supported on this backend');
122
+ }
123
+ ```
124
+
125
+ ## Edge Cases
126
+
127
+ - **Runtime Changes:** Capabilities are static per provider instance. If a backend configuration changes, the provider must be re-initialized.
128
+ - **Partial Support:** A capability might be `true` but only for specific users. Local validation still applies.
129
+ - **Provider Switching:** When switching providers (e.g., Medusa to Shopify), ensure the UI handles the change in capabilities instantly.
130
+
131
+ ## Performance
132
+
133
+ Capabilities are calculated once during provider initialization. Accessing `commerce.capabilities` is a zero-latency O(1) operation.
134
+
135
+ ## Accessibility
136
+
137
+ - **Disabled States:** When a capability is `false`, ensure inputs are correctly marked as `disabled` and provide an `aria-description` explaining why.
138
+ - **Dynamic Content:** Use `aria-live` regions if UI sections disappear based on backend capabilities during initialization.
139
+
140
+ ## See Also
141
+
142
+ - [Providers Overview](providers-overview.md)
143
+ - [Services Overview](services-overview.md)
@@ -0,0 +1,309 @@
1
+ # Category Customization & Inheritance
2
+
3
+ ## The Problem (Simple Version)
4
+
5
+ Your store sells **only shoes**. But the backend (Medusa) gives you 10 categories: "Formales", "Deportivos", "Sandalias", "Accesorios", etc.
6
+
7
+ **Question**: What do you do with categories you don't use?
8
+
9
+ ### Option A: Take It Or Leave It (Bad)
10
+ - Accept all 10 categories as-is
11
+ - Can't hide unwanted ones
12
+ - Can't rename for your market (Spain: "fundas" vs USA: "covers")
13
+ - **Result**: Broken UX, confused customers
14
+
15
+ ### Option B: Personalize Inheritance (Good ✨)
16
+ - Get all categories from backend
17
+ - But you can:
18
+ - **Hide** ones you don't need
19
+ - **Rename** to match your market
20
+ - **Add new** custom categories if Medusa doesn't have them
21
+ - **Toggle** inheritance on/off
22
+
23
+ **Result**: Flexible, multi-market ready, no backend changes needed.
24
+
25
+ ---
26
+
27
+ ## Real-World Example
28
+
29
+ ### Scenario: "Zapatos Cubanos Online"
30
+
31
+ **Backend (Medusa) provides:**
32
+ ```
33
+ - Formales
34
+ - Deportivos
35
+ - Sandalias
36
+ - Accesorios
37
+ ```
38
+
39
+ **You customize in dashboard:**
40
+ ```
41
+ ❌ Hide "Sandalias" (you don't sell sandals)
42
+ ✏️ Rename "Accesorios" → "Extras"
43
+ ✨ Add "Bestsellers" (custom category)
44
+ ```
45
+
46
+ **Customer sees in storefront:**
47
+ ```
48
+ - Formales
49
+ - Deportivos
50
+ - Extras (renamed)
51
+ - Bestsellers (custom)
52
+ ```
53
+
54
+ **Database stores (in metadata):**
55
+ ```json
56
+ {
57
+ "categoryCustomizations": {
58
+ "hidden": ["sandalias"],
59
+ "renamed": { "accesorios": "Extras" },
60
+ "custom": [
61
+ { "id": "bestsellers", "name": "Bestsellers", ... }
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## How It Works (The 5-Phase System)
70
+
71
+ ### Phase 1: Define Vocabulary (L1 — Types)
72
+ ```
73
+ What is a customization?
74
+ → { hidden: ["id1", "id2"], renamed: {id: "newname"}, custom: [{...}] }
75
+ ```
76
+
77
+ We define this **once** in `@thorprovider/types` so every layer speaks the same language.
78
+
79
+ ---
80
+
81
+ ### Phase 2: Store in Backend (L2 — Metadata Adapter)
82
+ ```
83
+ Backend (Medusa, Shopify, etc.)
84
+ ↓
85
+ BackendMetadataAdapter
86
+ ↓ (reads/writes JSON in sales_channel.metadata)
87
+ Your customizations
88
+ ```
89
+
90
+ **Key principle**: We use the backend's native **metadata storage**, not a separate database.
91
+
92
+ Why?
93
+ - **Simple**: No external SQLite/Supabase
94
+ - **Multi-tenant ready**: Each sales channel has its own metadata
95
+ - **Agnóstic**: Medusa, Shopify, WordPress — same interface
96
+
97
+ ---
98
+
99
+ ### Phase 3: Merge Logic (L2 — Service)
100
+ ```
101
+ Input:
102
+ baseCategories = [Formales, Deportivos, Sandalias]
103
+ customizations = { hidden: [Sandalias], renamed: {...}, custom: [...] }
104
+
105
+ Function mergeCategories(base, customizations):
106
+ 1. Remove hidden categories
107
+ 2. Rename categories per config
108
+ 3. Add custom categories
109
+ 4. Return final list
110
+
111
+ Output:
112
+ effectiveCategories = [Formales, Deportivos, Extras, Bestsellers]
113
+ ```
114
+
115
+ **Why L2?** This is business logic (not UI, not type definition). It belongs with the adapter.
116
+
117
+ ---
118
+
119
+ ### Phase 4: Expose via API (L5 — Route Handler)
120
+ ```
121
+ GET /api/admin/categories
122
+ → Returns merged categories (dashboard reads state)
123
+
124
+ POST /api/admin/categories
125
+ → Dashboard sends new customizations, backend saves them
126
+
127
+ PUT /api/admin/categories
128
+ → (Optional) Update a single category (hide/rename)
129
+ ```
130
+
131
+ **Why L5?** Only the app knows HTTP. Only the app can validate requests.
132
+
133
+ ---
134
+
135
+ ### Phase 5: Dashboard UI (L4 — Component)
136
+ ```
137
+ ┌─────────────────────────────────┐
138
+ │ ⚙️ Manage Categories │
139
+ ├─────────────────────────────────┤
140
+ │ ☑️ Use inherited categories │ ← Toggle
141
+ ├─────────────────────────────────┤
142
+ │ Base Categories: │
143
+ │ ☑️ Formales │
144
+ │ ☑️ Deportivos │
145
+ │ ☐ Sandalias [HIDDEN] │
146
+ │ (Extras) ← Renamed from "Accesorios"
147
+ ├─────────────────────────────────┤
148
+ │ Custom Categories: │
149
+ │ ✨ Bestsellers │
150
+ │ [Edit] [Delete] │
151
+ ├─────────────────────────────────┤
152
+ │ │
153
+ │ [+ Add Custom] [Save] │
154
+ └─────────────────────────────────┘
155
+ ```
156
+
157
+ **Why L4?** Reusable component (could work in different apps). Doesn't know about HTTP/databases.
158
+
159
+ ---
160
+
161
+ ## Why This Architecture Is "Agnóstic"
162
+
163
+ **Agnóstic** = "Works with any backend, no vendor lock-in."
164
+
165
+ ### Data Flow
166
+
167
+ ```
168
+ ┌─────────────────────┐
169
+ │ Medusa │ (or Shopify, WordPress)
170
+ │ sales_channel. │
171
+ │ metadata │
172
+ └──────────┬──────────┘
173
+ │ (Phase 2: BackendMetadataAdapter)
174
+ ↓
175
+ ┌──────────────────────────────────────┐
176
+ │ BackendMetadataAdapter (Interface)│
177
+ │ - getMetadata() │
178
+ │ - setMetadata() │
179
+ │ (Same methods for all backends) │
180
+ └──────────┬───────────────────────────┘
181
+ │ (Phase 3: mergeCategories)
182
+ ↓
183
+ ┌──────────────────────────────────────┐
184
+ │ CategoryService │
185
+ │ mergeCategories(base, custom) │
186
+ │ (Pure function, no dependencies) │
187
+ └──────────┬───────────────────────────┘
188
+ │ (Phase 4: API exposes)
189
+ ↓
190
+ ┌──────────────────────────────────────┐
191
+ │ /api/admin/categories │
192
+ │ (GET merged, POST customizations) │
193
+ └──────────┬───────────────────────────┘
194
+ │ (Phase 5: UI consumes)
195
+ ↓
196
+ ┌──────────────────────────────────────┐
197
+ │ CategoryCustomization Component │
198
+ │ (Dashboard controls) │
199
+ └──────────────────────────────────────┘
200
+ ```
201
+
202
+ ### If You Change Backend: Medusa → Shopify
203
+
204
+ **What changes:**
205
+ - Phase 2 implementation (MedusaMetadataAdapter → ShopifyMetadataAdapter)
206
+
207
+ **What stays the same:**
208
+ - Phase 1 (types)
209
+ - Phase 3 (mergeCategories function)
210
+ - Phase 4 (API route)
211
+ - Phase 5 (UI component)
212
+
213
+ **Why?** All non-Phase-2 code depends on the **interface**, not the **implementation**.
214
+
215
+ ---
216
+
217
+ ## Key Concepts
218
+
219
+ ### 1. Inheritance = Default Categories
220
+
221
+ The backend provides a default set (e.g., Medusa categories). You inherit them by default, but can modify.
222
+
223
+ ### 2. Customization = Your Changes
224
+
225
+ Your modifications (hidden, renamed, custom) are stored **independently** from base categories.
226
+
227
+ This way:
228
+ - Base categories can update from Medusa (new image, new subcategories)
229
+ - Your customizations persist and reapply on top
230
+
231
+ ### 3. Effective Categories = Result
232
+
233
+ What the storefront **actually sees** = base + customizations merged.
234
+
235
+ The storefront never knows about inheritance. It just fetches categories and displays them.
236
+
237
+ ### 4. Metadata Storage = No Extra Database
238
+
239
+ We use the backend's **metadata** field (exists in Medusa, Shopify, etc.).
240
+
241
+ ```
242
+ Medusa: sales_channel.metadata = { category_customizations: {...} }
243
+ Shopify: custom field or app data store (same interface)
244
+ ```
245
+
246
+ Result: One less database to maintain.
247
+
248
+ ---
249
+
250
+ ## Implementation Order (For Next Agent)
251
+
252
+ See `.development-guides/category-customization-implementation.md` for detailed step-by-step.
253
+
254
+ **TL;DR:**
255
+ 1. Add types in L1
256
+ 2. Extend metadata adapter in L2
257
+ 3. Create `mergeCategories()` service in L2
258
+ 4. Integrate into `CommerceProvider.getCategories()` in L2
259
+ 5. Create API route in L5
260
+ 6. Create UI component in L4
261
+ 7. Integrate into dashboard in L5
262
+
263
+ ---
264
+
265
+ ## FAQ
266
+
267
+ ### Q: Can I hide a category if customers already have it in their cart?
268
+
269
+ **A**: Yes, but consider adding a migration script:
270
+ - Categories hidden after purchase → show warning on checkout
271
+ - Don't render "orphan" categories in storefront
272
+ - Backend (Medusa) still serves the products (they exist, just not categorized)
273
+
274
+ ### Q: What if I rename a category but Medusa updates the base category name?
275
+
276
+ **A**: The renamed version wins. Custom > Base.
277
+
278
+ If you want to un-rename, just delete the entry from the `renamed` map.
279
+
280
+ ### Q: Can I have nested custom categories?
281
+
282
+ **A**: Yes, use `parentCategoryId` in custom category objects.
283
+
284
+ But ensure your storefront supports hierarchical rendering.
285
+
286
+ ### Q: What happens if I toggle inheritance off?
287
+
288
+ **A**: Only custom categories show. Base categories are ignored (but not deleted).
289
+
290
+ If you toggle back on, base categories reappear (minus any you've hidden).
291
+
292
+ ### Q: How do I add an image to a custom category?
293
+
294
+ **A**: Custom categories support `metadata: { image: "..." }`.
295
+
296
+ Storefront should render it like base categories.
297
+
298
+ ---
299
+
300
+ ## Related Files
301
+
302
+ - **Implementation plan**: `.development-guides/category-customization-implementation.md`
303
+ - **Types**: `packages/types/src/admin/category-customizations.ts`
304
+ - **Metadata adapter**: `packages/adapters/src/backend/BackendMetadataAdapter.ts`
305
+ - **Service**: `packages/adapters/src/services/category-service.ts`
306
+ - **Provider integration**: `packages/adapters/src/providers/medusa/index.ts` (getCategories)
307
+ - **API route**: `packages/core/app/api/admin/categories/route.ts`
308
+ - **Component**: `packages/components/src/patterns/modules/dashboard/CategoryCustomization.tsx`
309
+