@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.
- package/README.md +15 -62
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +47 -9
- package/dist/registry.d.ts +3 -4
- package/dist/registry.js +7 -14
- package/dist/services.d.ts +21 -1
- package/dist/services.js +38 -1
- package/dist/tools/adapter.d.ts +19 -2
- package/dist/tools/adapter.js +49 -10
- package/dist/tools/cartridges/index.js +11 -3
- package/dist/tools/diagnostics/config-inspect.js +2 -2
- package/dist/tools/diagnostics/debug-list-sessions.js +1 -0
- package/dist/tools/diagnostics/debug-start-session.js +13 -2
- package/dist/tools/diagnostics/log-watch-registry.d.ts +3 -0
- package/dist/tools/diagnostics/log-watch-registry.js +1 -0
- package/dist/tools/diagnostics/logs-watch-list.js +1 -0
- package/dist/tools/diagnostics/logs-watch-start.js +6 -1
- package/dist/tools/diagnostics/mrt-log-watch-registry.d.ts +3 -0
- package/dist/tools/diagnostics/mrt-log-watch-registry.js +0 -0
- package/dist/tools/diagnostics/mrt-logs-watch-list.js +1 -0
- package/dist/tools/diagnostics/mrt-logs-watch-start.js +7 -1
- package/dist/tools/diagnostics/session-registry.d.ts +3 -0
- package/dist/tools/diagnostics/session-registry.js +2 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/mrt/index.js +4 -0
- package/dist/tools/project-context.d.ts +42 -11
- package/dist/tools/project-context.js +48 -15
- package/dist/tools/scapi/metrics-get.js +1 -1
- package/dist/tools/scapi/scapi-custom-api-generate-scaffold.d.ts +4 -0
- package/dist/tools/scapi/scapi-custom-api-generate-scaffold.js +27 -7
- package/dist/tools/scapi/scapi-custom-apis-get-status.js +1 -1
- package/dist/tools/scapi/scapi-schemas-list.js +1 -0
- package/dist/utils/constants.d.ts +2 -13
- package/dist/utils/constants.js +1 -20
- package/oclif.manifest.json +2 -2
- package/package.json +3 -3
- package/content/sfnext/auth.md +0 -62
- package/content/sfnext/components.md +0 -123
- package/content/sfnext/config.md +0 -180
- package/content/sfnext/data-fetching.md +0 -323
- package/content/sfnext/extensions.md +0 -80
- package/content/sfnext/i18n.md +0 -121
- package/content/sfnext/page-designer.md +0 -78
- package/content/sfnext/performance.md +0 -80
- package/content/sfnext/pitfalls.md +0 -141
- package/content/sfnext/quick-reference.md +0 -226
- package/content/sfnext/state-management.md +0 -75
- package/content/sfnext/styling.md +0 -51
- package/content/sfnext/testing.md +0 -232
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +0 -24
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +0 -53
- package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +0 -45
- package/dist/tools/storefrontnext/figma/figma-to-component/index.js +0 -332
- package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +0 -40
- package/dist/tools/storefrontnext/figma/generate-component/decision.js +0 -312
- package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +0 -9
- package/dist/tools/storefrontnext/figma/generate-component/formatter.js +0 -92
- package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +0 -117
- package/dist/tools/storefrontnext/figma/generate-component/index.js +0 -102
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +0 -71
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +0 -260
- package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +0 -64
- package/dist/tools/storefrontnext/figma/map-tokens/index.js +0 -242
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +0 -65
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +0 -268
- package/dist/tools/storefrontnext/index.d.ts +0 -30
- package/dist/tools/storefrontnext/index.js +0 -28
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.d.ts +0 -169
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +0 -567
- package/dist/tools/storefrontnext/page-designer-decorator/index.d.ts +0 -259
- package/dist/tools/storefrontnext/page-designer-decorator/index.js +0 -622
- package/dist/tools/storefrontnext/page-designer-decorator/rules/1-mode-selection.d.ts +0 -8
- package/dist/tools/storefrontnext/page-designer-decorator/rules/1-mode-selection.js +0 -65
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2a-auto-mode.d.ts +0 -13
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2a-auto-mode.js +0 -87
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-0-interactive-overview.d.ts +0 -4
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-0-interactive-overview.js +0 -55
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-1-interactive-analyze.d.ts +0 -22
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-1-interactive-analyze.js +0 -109
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-2-interactive-select-props.d.ts +0 -21
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-2-interactive-select-props.js +0 -60
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-3-interactive-configure-attrs.d.ts +0 -27
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-3-interactive-configure-attrs.js +0 -68
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-4-interactive-configure-regions.d.ts +0 -4
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-4-interactive-configure-regions.js +0 -65
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-5-interactive-confirm-generation.d.ts +0 -11
- package/dist/tools/storefrontnext/page-designer-decorator/rules/2b-5-interactive-confirm-generation.js +0 -92
- package/dist/tools/storefrontnext/page-designer-decorator/rules.d.ts +0 -51
- package/dist/tools/storefrontnext/page-designer-decorator/rules.js +0 -70
- package/dist/tools/storefrontnext/page-designer-decorator/templates/decorator-generator.d.ts +0 -116
- package/dist/tools/storefrontnext/page-designer-decorator/templates/decorator-generator.js +0 -350
- package/dist/tools/storefrontnext/sfnext-development-guidelines.d.ts +0 -9
- package/dist/tools/storefrontnext/sfnext-development-guidelines.js +0 -141
- package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +0 -92
- package/dist/tools/storefrontnext/site-theming/color-contrast.js +0 -186
- package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +0 -16
- package/dist/tools/storefrontnext/site-theming/color-mapping.js +0 -131
- package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +0 -11
- package/dist/tools/storefrontnext/site-theming/guidance-merger.js +0 -78
- package/dist/tools/storefrontnext/site-theming/index.d.ts +0 -14
- package/dist/tools/storefrontnext/site-theming/index.js +0 -128
- package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +0 -16
- package/dist/tools/storefrontnext/site-theming/response-builder.js +0 -316
- package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +0 -64
- package/dist/tools/storefrontnext/site-theming/theming-store.js +0 -417
- package/dist/tools/storefrontnext/site-theming/types.d.ts +0 -36
- package/dist/tools/storefrontnext/site-theming/types.js +0 -7
package/content/sfnext/auth.md
DELETED
|
@@ -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
|
package/content/sfnext/config.md
DELETED
|
@@ -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.
|