@salesforce/b2c-dx-mcp 2.0.0 → 2.1.0

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 (108) hide show
  1. package/README.md +15 -62
  2. package/dist/commands/mcp.d.ts +1 -0
  3. package/dist/commands/mcp.js +47 -9
  4. package/dist/registry.d.ts +3 -4
  5. package/dist/registry.js +7 -14
  6. package/dist/services.d.ts +21 -1
  7. package/dist/services.js +38 -1
  8. package/dist/tools/adapter.d.ts +19 -2
  9. package/dist/tools/adapter.js +49 -10
  10. package/dist/tools/cartridges/index.js +11 -3
  11. package/dist/tools/diagnostics/config-inspect.js +2 -2
  12. package/dist/tools/diagnostics/debug-list-sessions.js +1 -0
  13. package/dist/tools/diagnostics/debug-start-session.js +13 -2
  14. package/dist/tools/diagnostics/log-watch-registry.d.ts +3 -0
  15. package/dist/tools/diagnostics/log-watch-registry.js +1 -0
  16. package/dist/tools/diagnostics/logs-watch-list.js +1 -0
  17. package/dist/tools/diagnostics/logs-watch-start.js +6 -1
  18. package/dist/tools/diagnostics/mrt-log-watch-registry.d.ts +3 -0
  19. package/dist/tools/diagnostics/mrt-log-watch-registry.js +0 -0
  20. package/dist/tools/diagnostics/mrt-logs-watch-list.js +1 -0
  21. package/dist/tools/diagnostics/mrt-logs-watch-start.js +7 -1
  22. package/dist/tools/diagnostics/session-registry.d.ts +3 -0
  23. package/dist/tools/diagnostics/session-registry.js +2 -1
  24. package/dist/tools/index.d.ts +0 -1
  25. package/dist/tools/index.js +0 -1
  26. package/dist/tools/mrt/index.js +4 -0
  27. package/dist/tools/project-context.d.ts +42 -11
  28. package/dist/tools/project-context.js +48 -15
  29. package/dist/tools/scapi/metrics-get.js +1 -1
  30. package/dist/tools/scapi/scapi-custom-api-generate-scaffold.d.ts +4 -0
  31. package/dist/tools/scapi/scapi-custom-api-generate-scaffold.js +27 -7
  32. package/dist/tools/scapi/scapi-custom-apis-get-status.js +1 -1
  33. package/dist/tools/scapi/scapi-schemas-list.js +1 -0
  34. package/dist/utils/constants.d.ts +2 -13
  35. package/dist/utils/constants.js +1 -20
  36. package/oclif.manifest.json +2 -2
  37. package/package.json +3 -3
  38. package/content/sfnext/auth.md +0 -62
  39. package/content/sfnext/components.md +0 -123
  40. package/content/sfnext/config.md +0 -180
  41. package/content/sfnext/data-fetching.md +0 -323
  42. package/content/sfnext/extensions.md +0 -80
  43. package/content/sfnext/i18n.md +0 -121
  44. package/content/sfnext/page-designer.md +0 -78
  45. package/content/sfnext/performance.md +0 -80
  46. package/content/sfnext/pitfalls.md +0 -141
  47. package/content/sfnext/quick-reference.md +0 -226
  48. package/content/sfnext/state-management.md +0 -75
  49. package/content/sfnext/styling.md +0 -51
  50. package/content/sfnext/testing.md +0 -232
  51. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +0 -24
  52. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +0 -53
  53. package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +0 -45
  54. package/dist/tools/storefrontnext/figma/figma-to-component/index.js +0 -332
  55. package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +0 -40
  56. package/dist/tools/storefrontnext/figma/generate-component/decision.js +0 -312
  57. package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +0 -9
  58. package/dist/tools/storefrontnext/figma/generate-component/formatter.js +0 -92
  59. package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +0 -117
  60. package/dist/tools/storefrontnext/figma/generate-component/index.js +0 -102
  61. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +0 -71
  62. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +0 -260
  63. package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +0 -64
  64. package/dist/tools/storefrontnext/figma/map-tokens/index.js +0 -242
  65. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +0 -65
  66. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +0 -268
  67. package/dist/tools/storefrontnext/index.d.ts +0 -30
  68. package/dist/tools/storefrontnext/index.js +0 -28
  69. package/dist/tools/storefrontnext/page-designer-decorator/analyzer.d.ts +0 -169
  70. package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +0 -567
  71. package/dist/tools/storefrontnext/page-designer-decorator/index.d.ts +0 -259
  72. package/dist/tools/storefrontnext/page-designer-decorator/index.js +0 -622
  73. package/dist/tools/storefrontnext/page-designer-decorator/rules/1-mode-selection.d.ts +0 -8
  74. package/dist/tools/storefrontnext/page-designer-decorator/rules/1-mode-selection.js +0 -65
  75. package/dist/tools/storefrontnext/page-designer-decorator/rules/2a-auto-mode.d.ts +0 -13
  76. package/dist/tools/storefrontnext/page-designer-decorator/rules/2a-auto-mode.js +0 -87
  77. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-0-interactive-overview.d.ts +0 -4
  78. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-0-interactive-overview.js +0 -55
  79. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-1-interactive-analyze.d.ts +0 -22
  80. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-1-interactive-analyze.js +0 -109
  81. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-2-interactive-select-props.d.ts +0 -21
  82. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-2-interactive-select-props.js +0 -60
  83. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-3-interactive-configure-attrs.d.ts +0 -27
  84. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-3-interactive-configure-attrs.js +0 -68
  85. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-4-interactive-configure-regions.d.ts +0 -4
  86. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-4-interactive-configure-regions.js +0 -65
  87. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-5-interactive-confirm-generation.d.ts +0 -11
  88. package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-5-interactive-confirm-generation.js +0 -92
  89. package/dist/tools/storefrontnext/page-designer-decorator/rules.d.ts +0 -51
  90. package/dist/tools/storefrontnext/page-designer-decorator/rules.js +0 -70
  91. package/dist/tools/storefrontnext/page-designer-decorator/templates/decorator-generator.d.ts +0 -116
  92. package/dist/tools/storefrontnext/page-designer-decorator/templates/decorator-generator.js +0 -350
  93. package/dist/tools/storefrontnext/sfnext-development-guidelines.d.ts +0 -9
  94. package/dist/tools/storefrontnext/sfnext-development-guidelines.js +0 -141
  95. package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +0 -92
  96. package/dist/tools/storefrontnext/site-theming/color-contrast.js +0 -186
  97. package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +0 -16
  98. package/dist/tools/storefrontnext/site-theming/color-mapping.js +0 -131
  99. package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +0 -11
  100. package/dist/tools/storefrontnext/site-theming/guidance-merger.js +0 -78
  101. package/dist/tools/storefrontnext/site-theming/index.d.ts +0 -14
  102. package/dist/tools/storefrontnext/site-theming/index.js +0 -128
  103. package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +0 -16
  104. package/dist/tools/storefrontnext/site-theming/response-builder.js +0 -316
  105. package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +0 -64
  106. package/dist/tools/storefrontnext/site-theming/theming-store.js +0 -417
  107. package/dist/tools/storefrontnext/site-theming/types.d.ts +0 -36
  108. package/dist/tools/storefrontnext/site-theming/types.js +0 -7
@@ -1,62 +0,0 @@
1
- # Authentication & Session Management
2
-
3
- ## Architecture
4
-
5
- Split-cookie architecture with server/client contexts:
6
-
7
- - **Server middleware** (`auth.server.ts`): Manages SLAS tokens, writes cookies
8
- - **Client middleware** (`auth.client.ts`): Reads cookies, maintains cache
9
- - **React Context** (`AuthProvider`): Provides auth state to components
10
-
11
- ## Cookie Design
12
-
13
- | Cookie Name | Purpose | User Type | Expiry | HttpOnly |
14
- |-------------|---------|-----------|--------|----------|
15
- | `cc-nx-g` | Guest refresh token | Guest | 30 days | No |
16
- | `cc-nx` | Registered refresh token | Registered | 90 days | No |
17
- | `cc-at` | Access token | Both | 30 min | No |
18
- | `usid` | User session ID | Both | Matches refresh | No |
19
- | `customerId` | Customer ID | Registered | Matches refresh | No |
20
-
21
- **Key Points**:
22
-
23
- - Only ONE refresh token exists (guest OR registered, never both)
24
- - User type derived from which refresh token exists
25
- - Cookies auto-namespaced with `siteId`
26
- - Tokens auto-refresh when expired
27
-
28
- ## Usage in Loaders/Actions
29
-
30
- ```typescript
31
- import { getAuth } from '@/middlewares/auth.server';
32
-
33
- export function loader({ context }: LoaderFunctionArgs) {
34
- const auth = getAuth(context);
35
-
36
- // Access auth properties
37
- const accessToken = auth.access_token;
38
- const customerId = auth.customer_id;
39
- const isGuest = auth.userType === 'guest';
40
- const isRegistered = auth.userType === 'registered';
41
-
42
- return { isGuest, customerId };
43
- }
44
- ```
45
-
46
- ## Usage in Components
47
-
48
- ```typescript
49
- import { useAuth } from '@/providers/auth';
50
-
51
- export function MyComponent() {
52
- const auth = useAuth();
53
-
54
- if (auth?.userType === 'guest') {
55
- return <LoginPrompt />;
56
- }
57
-
58
- return <div>Welcome, customer {auth?.customer_id}</div>;
59
- }
60
- ```
61
-
62
- **Reference:** See README-AUTH.md for complete authentication documentation.
@@ -1,123 +0,0 @@
1
- # Component Patterns
2
-
3
- ## Use the `createPage` HOC
4
-
5
- The `createPage` higher-order component standardizes page patterns with built-in Suspense and page key handling:
6
-
7
- ```typescript
8
- import { use } from 'react';
9
- import { createPage } from '@/components/create-page';
10
-
11
- // Define your view component
12
- function ProductView({
13
- product,
14
- category
15
- }: {
16
- product: Promise<Product>;
17
- category?: Promise<Category>
18
- }) {
19
- const productData = use(product);
20
- const categoryData = category ? use(category) : null;
21
-
22
- return (
23
- <div>
24
- <h1>{productData.name}</h1>
25
- {categoryData && <p>Category: {categoryData.name}</p>}
26
- </div>
27
- );
28
- }
29
-
30
- // Create page with fallback
31
- const ProductPage = createPage({
32
- component: ProductView,
33
- fallback: <ProductSkeleton />
34
- });
35
-
36
- export default ProductPage;
37
- ```
38
-
39
- **Benefits:**
40
-
41
- - Eliminates repetitive Suspense/Await boilerplate
42
- - Consistent loading states across pages
43
- - Built-in page key management for navigation transitions
44
- - Type-safe with full TypeScript support
45
-
46
- ## shadcn/ui Components
47
-
48
- **RULES**:
49
-
50
- - ✅ Add via: `npx shadcn@latest add <component-name>`
51
- - ❌ DO NOT modify `src/components/ui/` directly
52
- - ✅ Create custom components elsewhere
53
-
54
- ## Suspense Boundaries
55
-
56
- Use granular Suspense boundaries for better UX:
57
-
58
- ```typescript
59
- // ✅ RECOMMENDED - Multiple Suspense boundaries
60
- export default function ProductPage({ loaderData: { product, reviews } }) {
61
- return (
62
- <div>
63
- <Suspense fallback={<ProductHeaderSkeleton />}>
64
- <Await resolve={product}>
65
- {(data) => <ProductHeader product={data} />}
66
- </Await>
67
- </Suspense>
68
-
69
- <Suspense fallback={<ReviewsSkeleton />}>
70
- <Await resolve={reviews}>
71
- {(data) => <ProductReviews reviews={data} />}
72
- </Await>
73
- </Suspense>
74
- </div>
75
- );
76
- }
77
-
78
- // ⚠️ OK - Single Suspense boundary (less granular)
79
- export default createPage({
80
- component: ProductView,
81
- fallback: <ProductPageSkeleton />
82
- });
83
- ```
84
-
85
- ## File Organization
86
-
87
- ```
88
- src/components/product-tile/
89
- ├── index.tsx # Component
90
- ├── index.test.tsx # Tests
91
- └── stories/
92
- ├── index.stories.tsx # Storybook stories
93
- └── __snapshots__/ # Storybook snapshots (optional)
94
- └── product-tile-snapshot.tsx.snap
95
-
96
- # Skeleton components are separate components
97
- src/components/product-skeleton/
98
- ├── index.tsx
99
- ├── index.test.tsx
100
- └── stories/
101
- └── index.stories.tsx
102
- ```
103
-
104
- ## Styling
105
-
106
- **Tailwind CSS 4** is the only styling approach allowed. Use utility classes directly in components.
107
-
108
- **Key rules:**
109
-
110
- - ✅ Use Tailwind utility classes
111
- - ✅ Use `cn()` utility for conditional classes
112
- - ❌ NO inline styles, NO CSS modules, NO separate CSS files
113
-
114
- **See `styling` section for:** Tailwind CSS 4, Shadcn/ui components, icons, responsive design, theme configuration, dark mode, best practices
115
-
116
- ## Best Practices
117
-
118
- 1. **Extract view components** - Separate data handling from presentation
119
- 2. **Type safety** - Define proper TypeScript interfaces
120
- 3. **Consistent fallbacks** - Reusable skeleton components
121
- 4. **Colocate tests** - Keep tests next to components
122
- 5. **Story coverage** - Create stories for all reusable components
123
- 6. **Tailwind utilities only** - Use Tailwind CSS classes, avoid inline styles or CSS modules
@@ -1,180 +0,0 @@
1
- # Configuration Management
2
-
3
- ## Overview
4
-
5
- All configuration is centralized in `config.server.ts` with environment variable overrides via `.env` files. The configuration system provides type-safe access to app settings with automatic parsing and validation.
6
-
7
- ## Required Variables
8
-
9
- Copy `.env.default` to `.env` and set these required Commerce Cloud credentials:
10
-
11
- ```bash
12
- PUBLIC__app__commerce__api__clientId=your-client-id
13
- PUBLIC__app__commerce__api__organizationId=your-org-id
14
- PUBLIC__app__commerce__api__siteId=your-site-id
15
- PUBLIC__app__commerce__api__shortCode=your-short-code
16
- PUBLIC__app__defaultSiteId=your-site-id
17
- PUBLIC__app__commerce__sites='[{"id":"your-site-id","defaultLocale":"en-US","defaultCurrency":"USD","supportedLocales":[{"id":"en-US","preferredCurrency":"USD"}],"supportedCurrencies":["USD"]}]'
18
- ```
19
-
20
- **Note:** The `commerce.sites` array defines your site configuration including locales, currencies, and supported options. See `.env.default` for a complete example with multiple locales and currencies.
21
-
22
- ## Adding Configuration
23
-
24
- 1. **Define type in `src/config/schema.ts`**:
25
-
26
- ```typescript
27
- export type Config = {
28
- app: {
29
- myFeature: {
30
- enabled: boolean;
31
- maxItems: number;
32
- };
33
- };
34
- };
35
- ```
36
-
37
- 2. **Add defaults in `config.server.ts`**:
38
-
39
- ```typescript
40
- export default defineConfig({
41
- app: {
42
- myFeature: {
43
- enabled: false,
44
- maxItems: 10,
45
- },
46
- },
47
- });
48
- ```
49
-
50
- 3. **Override via environment variables**:
51
-
52
- ```bash
53
- PUBLIC__app__myFeature__enabled=true
54
- PUBLIC__app__myFeature__maxItems=20
55
- ```
56
-
57
- ## Usage Patterns
58
-
59
- **In React Components**:
60
-
61
- ```typescript
62
- import { useConfig } from '@/config';
63
-
64
- export function MyComponent() {
65
- const config = useConfig();
66
-
67
- if (config.myFeature.enabled) {
68
- const maxItems = config.myFeature.maxItems;
69
- // Your feature code
70
- }
71
- }
72
- ```
73
-
74
- **In Server Loaders/Actions**:
75
-
76
- ```typescript
77
- import { getConfig } from '@/config';
78
-
79
- export function loader({ context }: LoaderFunctionArgs) {
80
- const config = getConfig(context);
81
-
82
- if (config.myFeature.enabled) {
83
- // Your loader code
84
- }
85
- }
86
- ```
87
-
88
- **In Client Loaders**:
89
-
90
- ```typescript
91
- import { getConfig } from '@/config';
92
-
93
- export function clientLoader() {
94
- const config = getConfig(); // No context needed - uses window.__APP_CONFIG__
95
-
96
- if (config.myFeature.enabled) {
97
- // Your loader code
98
- }
99
- }
100
- ```
101
-
102
- **Note:** `getConfig()` and `useConfig()` return `AppConfig` which is the `app` section of the full `Config` type. So you access properties directly (e.g., `config.myFeature.enabled`) without the `app` prefix.
103
-
104
- ## Environment Variable Rules
105
-
106
- Use the `PUBLIC__` prefix with double underscores (`__`) to set any config path:
107
-
108
- ```bash
109
- # Environment variable → Config path (in Config type) → Access via getConfig()/useConfig()
110
- PUBLIC__app__commerce__sites='[...]' → config.app.commerce.sites → config.commerce.sites
111
- PUBLIC__app__defaultSiteId=RefArchGlobal → config.app.defaultSiteId → config.defaultSiteId
112
- PUBLIC__app__myFeature__enabled=true → config.app.myFeature.enabled → config.myFeature.enabled
113
- ```
114
-
115
- **Multi-site Configuration Example:**
116
-
117
- ```bash
118
- PUBLIC__app__commerce__sites='[
119
- {
120
- "id": "RefArchGlobal",
121
- "defaultLocale": "en-US",
122
- "defaultCurrency": "USD",
123
- "supportedLocales": [
124
- {"id": "en-US", "preferredCurrency": "USD"},
125
- {"id": "de-DE", "preferredCurrency": "EUR"}
126
- ],
127
- "supportedCurrencies": ["USD", "EUR"]
128
- }
129
- ]'
130
- ```
131
-
132
- **Accessing Site Configuration:**
133
-
134
- ```typescript
135
- const config = getConfig(context);
136
- const currentSite = config.commerce.sites[0]; // Get first site
137
- const locale = currentSite.defaultLocale; // "en-US"
138
- const currency = currentSite.defaultCurrency; // "USD"
139
- ```
140
-
141
- Values are automatically parsed (numbers, booleans, JSON arrays/objects).
142
-
143
- Rules:
144
- 1. **`PUBLIC__` prefix**: Exposed to browser (client-safe values)
145
- 2. **No prefix**: Server-only (secrets, never exposed)
146
- 3. **`__` separator**: Navigate nested paths (`PUBLIC__app__commerce__sites`)
147
- 4. **Case-insensitive**: All casings work (normalized to match `config.server.ts`)
148
- 5. **Auto-parsing**: Strings, numbers, booleans, JSON arrays/objects
149
- 6. **Validation**: Paths must exist in `config.server.ts` (prevents typos)
150
- 7. **Depth limit**: Maximum 10 levels deep (use JSON values for deeper nesting)
151
- 8. **Path precedence**: More specific paths override less specific ones
152
- 9. **Protected paths**: `app__engagement` cannot be overridden via environment variables
153
- 10. **MRT limits**: Variable names max 512 characters, total PUBLIC__ values max 32KB
154
-
155
- **Note:** Site configuration (locales, currencies) is now managed via `PUBLIC__app__commerce__sites` array instead of individual `PUBLIC__app__site__locale` variables. This enables multi-site support.
156
-
157
- **Setting nested objects with JSON:**
158
-
159
- ```bash
160
- # Instead of multiple variables:
161
- PUBLIC__app__myFeature__option1=value1
162
- PUBLIC__app__myFeature__option2=value2
163
-
164
- # Use a single JSON value:
165
- PUBLIC__app__myFeature='{"option1":"value1","option2":"value2","nested":{"enabled":true}}'
166
- ```
167
-
168
- ## Security
169
-
170
- ```bash
171
- # ✅ Safe for client (PUBLIC__ prefix)
172
- PUBLIC__app__commerce__api__clientId=abc123
173
-
174
- # ✅ Server-only (no prefix)
175
- COMMERCE_API_SLAS_SECRET=your-secret
176
- ```
177
-
178
- Read server-only secrets directly from `process.env` - never add to config.
179
-
180
- **Reference:** See src/config/README.md for complete configuration documentation.
@@ -1,323 +0,0 @@
1
- # Data Fetching Patterns
2
-
3
- ## Loader Functions
4
-
5
- **IMPORTANT**: This project **mandates server-only data loading**. Every UI route must only export a `loader` function.
6
-
7
- ### Critical Rule: Synchronous Loaders for Streaming
8
-
9
- **IMPORTANT**: Loaders should be **synchronous functions that return objects containing promises**, NOT async functions. This enables non-blocking page transitions and streaming SSR.
10
-
11
- ```typescript
12
- // ✅ CORRECT - Synchronous loader returning promises
13
- export function loader({ context }: LoaderFunctionArgs): ProductPageData {
14
- const clients = createApiClients(context);
15
- return {
16
- product: clients.shopperProducts.getProduct({...}), // Promise - streams
17
- reviews: clients.shopperProducts.getReviews({...}), // Promise - streams
18
- };
19
- }
20
-
21
- // ❌ AVOID - Async loader blocks page transitions
22
- export async function loader({ context }: LoaderFunctionArgs): Promise<ProductPageData> {
23
- const product = await clients.shopperProducts.getProduct({...}); // Blocks!
24
- return { product };
25
- }
26
- ```
27
-
28
- **Why this matters:**
29
- - Async loaders with `await` **block the entire page transition** until all data resolves
30
- - Synchronous loaders returning promises allow React to **stream data progressively**
31
- - Each promise resolves independently, enabling granular Suspense boundaries
32
- - Users see content as it becomes available, not all at once
33
-
34
- **Behavior**:
35
- - Initial load: Runs on server (SSR)
36
- - Navigation: Runs on server (XHR/fetch to server)
37
- - SCAPI requests always on MRT
38
-
39
- ## Data Loading Strategies
40
-
41
- ### Pattern 1: Awaited Data (Blocking)
42
-
43
- ```typescript
44
- // ⚠️ BLOCKS rendering until all data is ready
45
- export async function loader({ params, context }: LoaderFunctionArgs) {
46
- const clients = createApiClients(context);
47
- return {
48
- product: await clients.shopperProducts.getProduct({
49
- params: { path: { id: params.productId } }
50
- }).then(({ data }) => data)
51
- };
52
- }
53
- ```
54
-
55
- **Use when:** Critical data must be available before rendering (SEO, above-the-fold content)
56
-
57
- ### Pattern 2: Deferred Data (Streaming)
58
-
59
- ```typescript
60
- // ✅ RECOMMENDED - Streams data progressively
61
- export function loader({ params, context }: LoaderFunctionArgs) {
62
- const clients = createApiClients(context);
63
- return {
64
- // Return promises directly - they'll stream to client
65
- product: clients.shopperProducts.getProduct({
66
- params: { path: { id: params.productId } }
67
- }).then(({ data }) => data),
68
-
69
- reviews: clients.shopperProducts.getReviews({
70
- params: { path: { id: params.productId } }
71
- }).then(({ data }) => data)
72
- };
73
- }
74
- ```
75
-
76
- **Use when:** Non-critical data can load after initial render
77
-
78
- ### Pattern 3: Mixed Strategy
79
-
80
- ```typescript
81
- // ✅ BEST OF BOTH - Critical data awaited, rest streamed
82
- export async function loader({ params, context }: LoaderFunctionArgs) {
83
- const clients = createApiClients(context);
84
-
85
- // Await critical data
86
- const product = await clients.shopperProducts.getProduct({
87
- params: { path: { id: params.productId } }
88
- }).then(({ data }) => data);
89
-
90
- return {
91
- product, // Resolved
92
- reviews: clients.shopperProducts.getReviews({
93
- params: { path: { id: params.productId } }
94
- }).then(({ data }) => data), // Streamed
95
- recommendations: clients.shopperProducts.getRecommendations({
96
- params: { path: { id: params.productId } }
97
- }).then(({ data }) => data) // Streamed
98
- };
99
- }
100
- ```
101
-
102
- ## Action Functions
103
-
104
- Handle mutations (form submissions, cart updates):
105
-
106
- ```typescript
107
- import {data, redirect} from 'react-router';
108
-
109
- export async function action({request, context}: ActionFunctionArgs) {
110
- const formData = await request.formData();
111
- const productId = formData.get('productId') as string;
112
-
113
- const clients = createApiClients(context);
114
-
115
- try {
116
- await clients.shopperBasketsV2.addItemToBasket({
117
- params: {
118
- path: {basketId},
119
- body: {productId, quantity: 1},
120
- },
121
- });
122
-
123
- return data({success: true});
124
- } catch (error) {
125
- return data({success: false, error: error.message}, {status: 400});
126
- }
127
- }
128
- ```
129
-
130
- ## Interactive Data Fetching: useScapiFetcher
131
-
132
- For on-demand, user-triggered data fetching (after page load), use the `useScapiFetcher` hook instead of loaders.
133
-
134
- ### `loader` vs `useScapiFetcher`
135
-
136
- | Aspect | `loader` | `useScapiFetcher` |
137
- |--------|----------|-------------------|
138
- | **When it runs** | Route navigation (page load) | On-demand (user interaction) |
139
- | **Triggered by** | URL change | Component code (useEffect, button click) |
140
- | **Data availability** | Before/during component render (streamed) | After component mounts |
141
- | **Execution context** | Server (MRT) | Triggers server route |
142
- | **Use case** | Initial page data | Dynamic, interactive fetching |
143
-
144
- ### How `useScapiFetcher` Works
145
-
146
- ```text
147
- Component calls useScapiFetcher()
148
- ↓
149
- Hook builds URL: /resource/api/client/{encoded-params}
150
- ↓
151
- fetcher.load() or fetcher.submit()
152
- ↓
153
- resource.api.client.$resource.ts loader/action runs ON SERVER
154
- ↓
155
- createApiClients(context) makes SCAPI call (server-side)
156
- ↓
157
- JSON response returned to component
158
- ```
159
-
160
- **Important:** Even though you call `useScapiFetcher` from the browser, the actual SCAPI requests still happen **on the server** through the resource route, keeping credentials secure.
161
-
162
- ### Example: Search Suggestions
163
-
164
- ```typescript
165
- import { useScapiFetcher } from '@/hooks/use-scapi-fetcher';
166
- import { useMemo, useCallback } from 'react';
167
-
168
- export function useSearchSuggestions({ q, limit, currency }) {
169
- // Prepare SCAPI parameters
170
- const parameters = useMemo(
171
- () => ({
172
- params: {
173
- query: { q, limit, currency }
174
- }
175
- }),
176
- [q, limit, currency]
177
- );
178
-
179
- // Hook automatically routes to server
180
- const fetcher = useScapiFetcher(
181
- 'shopperSearch', // SCAPI client
182
- 'getSearchSuggestions', // Method name
183
- parameters // Parameters
184
- );
185
-
186
- const refetch = useCallback(async () => {
187
- await fetcher.load(); // Triggers server request
188
- }, [fetcher]);
189
-
190
- return {
191
- data: fetcher.data,
192
- isLoading: fetcher.state === 'loading',
193
- refetch
194
- };
195
- }
196
- ```
197
-
198
- ### When to Use Each Approach
199
-
200
- | Scenario | Use |
201
- |----------|-----|
202
- | Load product data when visiting `/product/123` | `loader` |
203
- | Load checkout data | `loader` |
204
- | Search suggestions as user types | `useScapiFetcher` |
205
- | Update customer profile in modal | `useScapiFetcher` |
206
- | Load recommendations after page loads | `useScapiFetcher` |
207
- | Fetch bonus products when modal opens | `useScapiFetcher` |
208
- | Infinite scroll / Load more | `useScapiFetcher` |
209
-
210
- ### Timeline Comparison
211
-
212
- ```text
213
- ┌─────────────────────────────────────────────────────────────────┐
214
- │ loader (Server) │
215
- ├─────────────────────────────────────────────────────────────────┤
216
- │ User clicks link → Server loader() → Stream data → Page render │
217
- │ │
218
- │ Timeline: [navigate] → [server fetch] → [stream to client] │
219
- │ │
220
- │ Data available: Streamed during render via Suspense │
221
- └─────────────────────────────────────────────────────────────────┘
222
-
223
- ┌─────────────────────────────────────────────────────────────────┐
224
- │ useScapiFetcher │
225
- ├─────────────────────────────────────────────────────────────────┤
226
- │ Page loads → Component mounts → User types → fetcher.load() │
227
- │ │
228
- │ Timeline: [render] → [user action] → [fetch] → [re-render] │
229
- │ │
230
- │ Data available: AFTER user action, component re-renders │
231
- └─────────────────────────────────────────────────────────────────┘
232
- ```
233
-
234
- ## API Client Usage
235
-
236
- Always use `createApiClients(context)`:
237
-
238
- ```typescript
239
- import { createApiClients } from '@/lib/api-clients';
240
-
241
- export function loader({ context }: LoaderFunctionArgs) {
242
- const clients = createApiClients(context);
243
-
244
- // All SCAPI clients with full type safety:
245
- clients.shopperProducts.getProduct({...});
246
- clients.shopperCustomers.getCustomer({...});
247
- clients.shopperBasketsV2.getBasket({...});
248
- clients.shopperSearch.productSearch({...});
249
- clients.shopperOrders.getOrder({...});
250
- }
251
- ```
252
-
253
- ## Parallel vs Sequential
254
-
255
- ```typescript
256
- // ✅ GOOD - Parallel requests
257
- export function loader({ context }: LoaderFunctionArgs) {
258
- const clients = createApiClients(context);
259
-
260
- return {
261
- product: clients.shopperProducts.getProduct({...}),
262
- reviews: clients.shopperProducts.getReviews({...}),
263
- recommendations: clients.shopperProducts.getRecommendations({...})
264
- };
265
- // All three requests start simultaneously
266
- }
267
-
268
- // ❌ BAD - Sequential requests
269
- export async function loader({ context }: LoaderFunctionArgs) {
270
- const clients = createApiClients(context);
271
-
272
- const product = await clients.shopperProducts.getProduct({...});
273
- const reviews = await clients.shopperProducts.getReviews({...});
274
- const recommendations = await clients.shopperProducts.getRecommendations({...});
275
-
276
- return { product, reviews, recommendations };
277
- // Each request waits for the previous to complete
278
- }
279
- ```
280
-
281
- ## Understanding Data Flow
282
-
283
- ### Initial Page Load (SSR)
284
-
285
- ```text
286
- Browser → MRT Server
287
- ↓
288
- loader() runs on server
289
- ↓
290
- SCAPI requests on MRT
291
- ↓
292
- HTML response → Browser
293
- ```
294
-
295
- **Key characteristics:**
296
- - The `loader()` runs on the server
297
- - SCAPI requests happen server-side (direct in production, proxied in dev)
298
- - Full HTML is returned to browser
299
- - Client hydrates the HTML
300
-
301
- ### Subsequent Navigation (SPA)
302
-
303
- All routes use server `loader` for both SSR and SPA navigation:
304
-
305
- ```text
306
- User clicks link → React Router intercepts
307
- ↓
308
- Browser makes fetch() to server
309
- ↓
310
- MRT Server receives request
311
- ↓
312
- Same loader() runs on server
313
- ↓
314
- SCAPI requests on MRT
315
- ↓
316
- JSON response → Browser
317
- ↓
318
- React updates DOM
319
- ```
320
-
321
- **Key Point:** The loader function code is identical for both SSR and SPA navigation. The only difference is the response format (HTML vs JSON). This is why SCAPI credentials stay secure and MRT orchestration works consistently.
322
-
323
- **Reference:** See README-DATA.md for complete data fetching documentation.