@salesforce/storefront-next-runtime 0.2.0 → 0.3.0-alpha.1
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/dist/DesignFrame.js +6 -2
- package/dist/DesignFrame.js.map +1 -1
- package/dist/DesignRegion.js +2 -1
- package/dist/DesignRegion.js.map +1 -1
- package/dist/apply-url-config.js +4 -4
- package/dist/apply-url-config.js.map +1 -1
- package/dist/component.types.d.ts +6 -0
- package/dist/component.types.d.ts.map +1 -1
- package/dist/config-load.d.ts +27 -0
- package/dist/config-load.d.ts.map +1 -0
- package/dist/config-load.js +3 -0
- package/dist/config.d.ts +248 -1
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +432 -0
- package/dist/config.js.map +1 -0
- package/dist/design-data.d.ts +40 -27
- package/dist/design-data.d.ts.map +1 -1
- package/dist/design-data.js +50 -26
- package/dist/design-data.js.map +1 -1
- package/dist/design-react-core.d.ts +2 -2
- package/dist/design-react-core.js +3 -1
- package/dist/design-react-core.js.map +1 -1
- package/dist/events.d.ts +9 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +6 -6
- package/dist/events.js.map +1 -1
- package/dist/load-config.js +42 -0
- package/dist/load-config.js.map +1 -0
- package/dist/routing.d.ts +1 -1
- package/dist/routing.d.ts.map +1 -1
- package/dist/routing.js +5 -38
- package/dist/routing.js.map +1 -1
- package/dist/scapi.d.ts +8 -0
- package/dist/scapi.d.ts.map +1 -1
- package/dist/scapi.js +1 -1
- package/dist/scapi.js.map +1 -1
- package/dist/schema.d.ts +78 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/{multi-site.d.ts → site-context.d.ts} +85 -60
- package/dist/site-context.d.ts.map +1 -0
- package/dist/{multi-site.js → site-context.js} +67 -41
- package/dist/site-context.js.map +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -4
- package/dist/multi-site.d.ts.map +0 -1
- package/dist/multi-site.js.map +0 -1
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/config/schema.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Base configuration type for storefront-next projects.
|
|
4
|
+
*
|
|
5
|
+
* Generic parameter `App` represents the template's application config shape.
|
|
6
|
+
* The SDK does not prescribe what fields `app` must contain — templates define
|
|
7
|
+
* their own `AppConfig` type with SCAPI credentials, pages, features, etc.
|
|
8
|
+
* and pass it as `BaseConfig<AppConfig>`.
|
|
9
|
+
*
|
|
10
|
+
* The SDK accesses specific `app` fields (e.g., `commerce.api.clientId`) at
|
|
11
|
+
* runtime via the middleware validation, not via compile-time type constraints.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam App - The template's application config shape (defaults to `Record<string, unknown>`)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // In the template's types file:
|
|
17
|
+
* type AppConfig = { commerce: { api: {...} }; pages: {...}; features: {...} };
|
|
18
|
+
* type Config = BaseConfig<AppConfig>;
|
|
19
|
+
*
|
|
20
|
+
* // In config.server.ts:
|
|
21
|
+
* export default defineConfig<Config>({ metadata: {...}, app: {...} });
|
|
22
|
+
*/
|
|
23
|
+
type BaseConfig<App extends Record<string, unknown> = Record<string, unknown>> = {
|
|
24
|
+
metadata: {
|
|
25
|
+
projectName: string;
|
|
26
|
+
projectSlug: string;
|
|
27
|
+
};
|
|
28
|
+
runtime?: {
|
|
29
|
+
defaultMrtProject?: string;
|
|
30
|
+
defaultMrtTarget?: string;
|
|
31
|
+
ssrOnly?: string[];
|
|
32
|
+
ssrShared?: string[];
|
|
33
|
+
ssrParameters?: Record<string, string | number | boolean>;
|
|
34
|
+
};
|
|
35
|
+
app: App;
|
|
36
|
+
};
|
|
37
|
+
interface DefineConfigOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Config paths that cannot be overridden by environment variables.
|
|
40
|
+
* Paths use double underscore separators and are matched case-insensitively.
|
|
41
|
+
*
|
|
42
|
+
* @example ['app__engagement'] — prevents PUBLIC__app__engagement__* from being set via env
|
|
43
|
+
*/
|
|
44
|
+
protectedPaths?: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Define a type-safe storefront configuration with IDE autocomplete.
|
|
48
|
+
*
|
|
49
|
+
* Automatically merges `PUBLIC__` prefixed environment variables into the config
|
|
50
|
+
* at load time. Validates env vars against the base config structure (strict mode —
|
|
51
|
+
* only allows overriding existing paths).
|
|
52
|
+
*
|
|
53
|
+
* Environment variables:
|
|
54
|
+
* - `PUBLIC__<path>` (optional): Override any config path using double underscore separators.
|
|
55
|
+
* e.g. `PUBLIC__app__commerce__api__clientId=abc123` maps to `config.app.commerce.api.clientId`
|
|
56
|
+
* - `PUBLIC__app__pages__cart__quantityUpdateDebounce=1000` maps to a number (optimistic JSON parsing)
|
|
57
|
+
* - `PUBLIC__app__features__socialLogin__providers=["Apple","Google"]` maps to an array
|
|
58
|
+
*
|
|
59
|
+
* @param config - The base configuration object with all defaults
|
|
60
|
+
* @param options - Optional settings (e.g., protectedPaths to prevent env var overrides)
|
|
61
|
+
* @returns The config with environment variable overrides merged in
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // In config.server.ts:
|
|
65
|
+
* import { defineConfig } from '@salesforce/storefront-next-runtime/config';
|
|
66
|
+
*
|
|
67
|
+
* export default defineConfig({
|
|
68
|
+
* metadata: { projectName: 'My Store', projectSlug: 'my-store' },
|
|
69
|
+
* app: {
|
|
70
|
+
* commerce: { api: { clientId: '', organizationId: '', shortCode: '' }, sites: [] },
|
|
71
|
+
* defaultSiteId: 'RefArch',
|
|
72
|
+
* },
|
|
73
|
+
* }, { protectedPaths: ['app__engagement'] });
|
|
74
|
+
*/
|
|
75
|
+
declare function defineConfig<T extends BaseConfig>(config: T, options?: DefineConfigOptions): T;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { DefineConfigOptions as n, defineConfig as r, BaseConfig as t };
|
|
78
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":[],"mappings":";;AAsCA;;;;;;AAeA;AAuCA;;;;;;;;;;;;;KAtDY,uBAAuB,0BAA0B;;;;;;;;;;oBAUrC;;OAEf;;UAGQ,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuCD,uBAAuB,oBAAoB,aAAa,sBAAsB"}
|
|
@@ -1,12 +1,49 @@
|
|
|
1
1
|
import { n as Site$1, r as Url, t as Locale$1 } from "./types.js";
|
|
2
2
|
import { PropsWithChildren } from "react";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
4
4
|
import * as react_router0 from "react-router";
|
|
5
5
|
import { Cookie, MiddlewareFunction, RouterContextProvider } from "react-router";
|
|
6
6
|
import { RouteConfigEntry } from "@react-router/dev/routes";
|
|
7
7
|
|
|
8
|
-
//#region src/
|
|
8
|
+
//#region src/site-context/types.d.ts
|
|
9
9
|
|
|
10
|
+
type Locale = Locale$1 & {
|
|
11
|
+
alias?: string;
|
|
12
|
+
};
|
|
13
|
+
type Site = Omit<Site$1, 'supportedLocales'> & {
|
|
14
|
+
name?: string;
|
|
15
|
+
alias?: string;
|
|
16
|
+
supportedLocales: Locale[];
|
|
17
|
+
};
|
|
18
|
+
type SiteContext = {
|
|
19
|
+
site: Site;
|
|
20
|
+
locale: Locale;
|
|
21
|
+
siteCookie: Cookie;
|
|
22
|
+
localeCookie: Cookie;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Configuration passed into the site context middleware
|
|
26
|
+
* Configured by the consumer
|
|
27
|
+
*/
|
|
28
|
+
type SiteConfig = {
|
|
29
|
+
sites: Site[];
|
|
30
|
+
defaultSiteId: string;
|
|
31
|
+
defaultLocale: string;
|
|
32
|
+
siteDetectionConfig?: DetectionConfig;
|
|
33
|
+
localeDetectionConfig?: DetectionConfig;
|
|
34
|
+
};
|
|
35
|
+
/** Detection method identifier (used for both site and locale detection) */
|
|
36
|
+
type DetectionMethod = 'path' | 'querystring' | 'cookie' | 'header';
|
|
37
|
+
type DetectionConfig = {
|
|
38
|
+
order: DetectionMethod[];
|
|
39
|
+
lookupFromPathIndex?: number;
|
|
40
|
+
lookupQuerystring?: string;
|
|
41
|
+
lookupCookie?: string;
|
|
42
|
+
lookupHeader?: string;
|
|
43
|
+
caches?: Array<'cookie'>;
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/site-context/site-context.d.ts
|
|
10
47
|
/**
|
|
11
48
|
* Provides the current site to the component tree.
|
|
12
49
|
* Follows the same pattern as CurrencyProvider.
|
|
@@ -18,18 +55,17 @@ declare function SiteProvider({
|
|
|
18
55
|
value,
|
|
19
56
|
children
|
|
20
57
|
}: PropsWithChildren<{
|
|
21
|
-
value: Site
|
|
22
|
-
}>):
|
|
58
|
+
value: Site;
|
|
59
|
+
}>): react_jsx_runtime2.JSX.Element;
|
|
23
60
|
/**
|
|
24
61
|
* React hook to get the current site.
|
|
25
62
|
* Returns undefined when no SiteProvider is mounted.
|
|
26
63
|
*/
|
|
27
|
-
declare function useSite(): Site
|
|
64
|
+
declare function useSite(): Site | undefined;
|
|
28
65
|
//#endregion
|
|
29
|
-
//#region src/
|
|
30
|
-
|
|
66
|
+
//#region src/site-context/apply-url-config.d.ts
|
|
31
67
|
/**
|
|
32
|
-
* Applies
|
|
68
|
+
* Applies site context URL configuration to a set of route entries.
|
|
33
69
|
*
|
|
34
70
|
* Wraps non-excluded routes under a parent route with the configured URL prefix
|
|
35
71
|
* (e.g. `/:siteId/:localeId`), while keeping excluded routes (action/resource by default)
|
|
@@ -48,12 +84,38 @@ declare function applyUrlConfig(options: {
|
|
|
48
84
|
wrapperFile: string;
|
|
49
85
|
}): RouteConfigEntry[];
|
|
50
86
|
//#endregion
|
|
51
|
-
//#region src/
|
|
52
|
-
|
|
87
|
+
//#region src/site-context/build-url.d.ts
|
|
53
88
|
/**
|
|
54
|
-
*
|
|
89
|
+
* Resolves a prefix template by replacing parameter placeholders with values.
|
|
90
|
+
* ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'
|
|
91
|
+
*/
|
|
92
|
+
declare function resolvePrefix(prefix: string, params: Record<string, string>): string;
|
|
93
|
+
/**
|
|
94
|
+
* Strips the URL prefix segments from a pathname based on a prefix pattern.
|
|
95
|
+
* Since all routes are configured with the prefix baked in, segment counting is sufficient.
|
|
55
96
|
*
|
|
56
|
-
*
|
|
97
|
+
* @param pathname - Full pathname (e.g. '/global/en-GB/checkout')
|
|
98
|
+
* @param prefixPattern - URL prefix pattern from config (e.g. '/:siteId/:localeId')
|
|
99
|
+
* @returns Pathname with prefix stripped (e.g. '/checkout'), or original if
|
|
100
|
+
* the pathname has fewer segments than the prefix
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId') // → '/checkout'
|
|
104
|
+
* stripPathPrefix('/checkout', '/:siteId/:localeId') // → '/checkout' (fewer segments → unchanged)
|
|
105
|
+
* stripPathPrefix('/checkout', '') // → '/checkout' (no prefix configured)
|
|
106
|
+
* stripPathPrefix('/', '/:siteId/:localeId') // → '/'
|
|
107
|
+
*/
|
|
108
|
+
declare function stripPathPrefix(pathname: string, prefixPattern: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Sanitize a resolved prefix from a pathname if present.
|
|
111
|
+
* sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'
|
|
112
|
+
* sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)
|
|
113
|
+
*/
|
|
114
|
+
declare function sanitizePrefix(pathname: string, pathPrefix: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* Builds a fully-qualified URL with site context prefix and search params.
|
|
117
|
+
*
|
|
118
|
+
* Only keys defined in urlConfig.search are set by site context. Any other query params
|
|
57
119
|
* already present on the `to` URL (including duplicate keys) are preserved as-is.
|
|
58
120
|
* e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'
|
|
59
121
|
* → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'
|
|
@@ -72,47 +134,10 @@ declare function buildUrl({
|
|
|
72
134
|
params: Record<string, string>;
|
|
73
135
|
}): string;
|
|
74
136
|
//#endregion
|
|
75
|
-
//#region src/
|
|
76
|
-
|
|
77
|
-
alias?: string;
|
|
78
|
-
};
|
|
79
|
-
type Site = Omit<Site$1, 'supportedLocales'> & {
|
|
80
|
-
name?: string;
|
|
81
|
-
alias?: string;
|
|
82
|
-
supportedLocales: Locale[];
|
|
83
|
-
};
|
|
84
|
-
type MultiSiteContext = {
|
|
85
|
-
site: Site;
|
|
86
|
-
locale: Locale;
|
|
87
|
-
siteCookie: Cookie;
|
|
88
|
-
localeCookie: Cookie;
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* Configuration passed into the multi-site middleware
|
|
92
|
-
* Configured by the consumer
|
|
93
|
-
*/
|
|
94
|
-
type MultiSiteConfig = {
|
|
95
|
-
sites: Site[];
|
|
96
|
-
defaultSiteId: string;
|
|
97
|
-
defaultLocale: string;
|
|
98
|
-
siteDetectionConfig?: DetectionConfig;
|
|
99
|
-
localeDetectionConfig?: DetectionConfig;
|
|
100
|
-
};
|
|
101
|
-
/** Detection method identifier (used for both site and locale detection) */
|
|
102
|
-
type DetectionMethod = 'path' | 'querystring' | 'cookie' | 'header';
|
|
103
|
-
type DetectionConfig = {
|
|
104
|
-
order: DetectionMethod[];
|
|
105
|
-
lookupFromPathIndex?: number;
|
|
106
|
-
lookupQuerystring?: string;
|
|
107
|
-
lookupCookie?: string;
|
|
108
|
-
lookupHeader?: string;
|
|
109
|
-
caches?: Array<'cookie'>;
|
|
110
|
-
};
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region src/multi-site/middleware.d.ts
|
|
113
|
-
declare const multiSiteContext: react_router0.RouterContext<MultiSiteContext | null>;
|
|
137
|
+
//#region src/site-context/middleware.d.ts
|
|
138
|
+
declare const siteContext: react_router0.RouterContext<SiteContext | null>;
|
|
114
139
|
/**
|
|
115
|
-
* Helper function to get
|
|
140
|
+
* Helper function to get site context cookies from router context.
|
|
116
141
|
* Useful in server actions and loaders that need to read/set cookies.
|
|
117
142
|
*
|
|
118
143
|
* @param context - Router context provider
|
|
@@ -121,7 +146,7 @@ declare const multiSiteContext: react_router0.RouterContext<MultiSiteContext | n
|
|
|
121
146
|
* @example
|
|
122
147
|
* ```typescript
|
|
123
148
|
* export const action: ActionFunction = async ({ request, context }) => {
|
|
124
|
-
* const cookies =
|
|
149
|
+
* const cookies = getSiteContextCookies(context);
|
|
125
150
|
* if (cookies) {
|
|
126
151
|
* const cookieHeader = await cookies.localeCookie.serialize(locale);
|
|
127
152
|
* // ... use cookieHeader
|
|
@@ -129,26 +154,26 @@ declare const multiSiteContext: react_router0.RouterContext<MultiSiteContext | n
|
|
|
129
154
|
* };
|
|
130
155
|
* ```
|
|
131
156
|
*/
|
|
132
|
-
declare function
|
|
157
|
+
declare function getSiteContextCookies(context: Readonly<RouterContextProvider>): {
|
|
133
158
|
siteCookie: react_router0.Cookie;
|
|
134
159
|
localeCookie: react_router0.Cookie;
|
|
135
160
|
} | null;
|
|
136
161
|
/**
|
|
137
|
-
* Creates a
|
|
162
|
+
* Creates a site context middleware that resolves the current site from
|
|
138
163
|
* the request (path, cookie, header, query, or default) and stores the
|
|
139
164
|
* result in the router context.
|
|
140
165
|
*
|
|
141
166
|
* Does not import or read from app config context; the consumer supplies config.
|
|
142
167
|
*/
|
|
143
|
-
declare function
|
|
168
|
+
declare function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response>;
|
|
144
169
|
//#endregion
|
|
145
|
-
//#region src/
|
|
170
|
+
//#region src/site-context/cookies.d.ts
|
|
146
171
|
/**
|
|
147
|
-
* WeakMap to pass resolved locale from
|
|
172
|
+
* WeakMap to pass resolved locale from site context middleware to i18next's findLocale.
|
|
148
173
|
* WeakMap allows garbage collection when requests are done.
|
|
149
174
|
* This is necessary because findLocale() only receives the Request object, not the router context.
|
|
150
175
|
*/
|
|
151
176
|
declare const requestToLocaleMap: WeakMap<Request, string>;
|
|
152
177
|
//#endregion
|
|
153
|
-
export { type DetectionConfig, type Locale, type
|
|
154
|
-
//# sourceMappingURL=
|
|
178
|
+
export { type DetectionConfig, type Locale, type Site, type SiteConfig, type SiteContext, SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
|
|
179
|
+
//# sourceMappingURL=site-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-context.d.ts","names":[],"sources":["../src/site-context/types.ts","../src/site-context/site-context.tsx","../src/site-context/apply-url-config.ts","../src/site-context/build-url.ts","../src/site-context/middleware.ts","../src/site-context/cookies.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAiCgB,KAbJ,MAAA,GAAS,QAaL,GAAA;EACE,KAAA,CAAA,EAAA,MAAA;CAAM;AAOZ,KAjBA,IAAA,GAAO,IAiBG,CAjBE,MAiBF,EAAA,kBAAA,CAAA,GAAA;EACX,IAAA,CAAA,EAAA,MAAA;EAGe,KAAA,CAAA,EAAA,MAAA;EACE,gBAAA,EAnBN,MAmBM,EAAA;CAAe;AAc/B,KA9BA,WAAA,GA8Be;EAGf,IAAA,EAhCF,IAgCE;UA/BA;cACI;gBACE;ACPlB,CAAA;;;;;AAAoF,KDcxE,UAAA,GCdwE;EAAA,KAAA,EDezE,ICfyE,EAAA;EASpE,aAAO,EAAA,MAAA;;wBDSG;0BACE;AE0E5B,CAAA;ACnBA;AAmBgB,KH5DJ,eAAA,GG4DY,MAAA,GAAA,aAAA,GAAA,QAAA,GAAA,QAAA;AACpB,KH1DQ,eAAA,GG0DR;EACA,KAAA,EH1DO,eG0DP,EAAA;EACA,mBAAA,CAAA,EAAA,MAAA;EAGY,iBAAA,CAAA,EAAA,MAAA;EACJ,YAAA,CAAA,EAAA,MAAA;EAAM,YAAA,CAAA,EAAA,MAAA;WH1DL;;;;;;;AA5Bb;;;;AAK2C,iBCnB3B,YAAA,CDmB2B;EAAA,KAAA;EAAA;AAiB3C,CAjB2C,ECnBO,iBDmBP,CAAA;EAc/B,KAAA,ECjCiE,IDiCjE;AAGZ,CAAA,CAAA,CAAA,ECpCoF,kBAAA,CAAA,GAAA,CAAA,ODqCzE;;;;ACrCX;AAA+B,iBASf,OAAA,CAAA,CATe,EASJ,IATI,GAAA,SAAA;;;;AC6F/B;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;AAEI,iBDFY,cAAA,CCEZ,OAAA,EAAA;EACA,MAAA,EDFQ,gBCER,EAAA;EAGY,SAAA,CAAA,EDJA,GCIA;EACJ,WAAA,EAAA,MAAA;CAAM,CAAA,EDHd,gBCGc,EAAA;;;;;AF3FlB;;iBEqBgB,aAAA,yBAAsC;;AD+DtD;;;;;;;;AC/DA;AA0BA;AAkBA;AAmBA;;;AAGI,iBAxCY,eAAA,CAwCZ,QAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;iBAtBY,cAAA;AC9EhB;AAsBA;;;;;;AAoEA;;;;;iBDOgB,QAAA;;;;;;cAMA;EEnFH,MAAA,EFoFD,MEpFC,CAAA,MAAmD,EAAA,MAAA,CAAA;;;;ALVhD,cIVH,WJUG,EIVQ,aAAA,CAAA,aJUR,CIVQ,WJUR,GAAA,IAAA,CAAA;;;AAQhB;;;;;AAmBA;AAGA;;;;ACpCA;;;;;;AAAoF,iBGkBpE,qBAAA,CHlBoE,OAAA,EGkBrC,QHlBqC,CGkB5B,qBHlB4B,CAAA,CAAA,EAAA;EASpE,UAAO,EGSgC,aAAA,CAAA,MHTxB;;;;ACoF/B;;;;;;iBEPgB,2BAAA,SAAoC,aAAa,mBAAmB;;;;;;;;AH7EpE,cIOH,kBJPkB,EIOA,OJPA,CIOA,OJPA,EAAA,MAAA,CAAA"}
|
|
@@ -3,7 +3,7 @@ import { createContext, useContext } from "react";
|
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createContext as createContext$1, createCookie } from "react-router";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/site-context/site-context.tsx
|
|
7
7
|
const SiteContext = createContext(void 0);
|
|
8
8
|
/**
|
|
9
9
|
* Provides the current site to the component tree.
|
|
@@ -27,7 +27,7 @@ function useSite() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
//#endregion
|
|
30
|
-
//#region src/
|
|
30
|
+
//#region src/site-context/build-url.ts
|
|
31
31
|
/**
|
|
32
32
|
* Parses search config string into key-value pairs, preserving ':param' placeholders.
|
|
33
33
|
* '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }
|
|
@@ -75,6 +75,28 @@ function resolvePrefix(prefix, params) {
|
|
|
75
75
|
return resolved;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
+
* Strips the URL prefix segments from a pathname based on a prefix pattern.
|
|
79
|
+
* Since all routes are configured with the prefix baked in, segment counting is sufficient.
|
|
80
|
+
*
|
|
81
|
+
* @param pathname - Full pathname (e.g. '/global/en-GB/checkout')
|
|
82
|
+
* @param prefixPattern - URL prefix pattern from config (e.g. '/:siteId/:localeId')
|
|
83
|
+
* @returns Pathname with prefix stripped (e.g. '/checkout'), or original if
|
|
84
|
+
* the pathname has fewer segments than the prefix
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId') // → '/checkout'
|
|
88
|
+
* stripPathPrefix('/checkout', '/:siteId/:localeId') // → '/checkout' (fewer segments → unchanged)
|
|
89
|
+
* stripPathPrefix('/checkout', '') // → '/checkout' (no prefix configured)
|
|
90
|
+
* stripPathPrefix('/', '/:siteId/:localeId') // → '/'
|
|
91
|
+
*/
|
|
92
|
+
function stripPathPrefix(pathname, prefixPattern) {
|
|
93
|
+
if (!prefixPattern) return pathname;
|
|
94
|
+
const prefixSegmentCount = prefixPattern.split("/").filter(Boolean).length;
|
|
95
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
96
|
+
if (pathSegments.length <= prefixSegmentCount) return pathSegments.length === prefixSegmentCount ? "/" : pathname;
|
|
97
|
+
return `/${pathSegments.slice(prefixSegmentCount).join("/")}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
78
100
|
* Sanitize a resolved prefix from a pathname if present.
|
|
79
101
|
* sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'
|
|
80
102
|
* sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)
|
|
@@ -86,9 +108,9 @@ function sanitizePrefix(pathname, pathPrefix) {
|
|
|
86
108
|
return pathname;
|
|
87
109
|
}
|
|
88
110
|
/**
|
|
89
|
-
* Builds a fully-qualified URL with
|
|
111
|
+
* Builds a fully-qualified URL with site context prefix and search params.
|
|
90
112
|
*
|
|
91
|
-
* Only keys defined in urlConfig.search are set by
|
|
113
|
+
* Only keys defined in urlConfig.search are set by site context. Any other query params
|
|
92
114
|
* already present on the `to` URL (including duplicate keys) are preserved as-is.
|
|
93
115
|
* e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'
|
|
94
116
|
* → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'
|
|
@@ -116,7 +138,7 @@ function buildUrl({ to, urlConfig, params }) {
|
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
//#endregion
|
|
119
|
-
//#region src/
|
|
141
|
+
//#region src/site-context/utils.ts
|
|
120
142
|
/**
|
|
121
143
|
* Extract a string value from the URL path segment at the given index.
|
|
122
144
|
*/
|
|
@@ -137,7 +159,7 @@ async function readCookieFromRequest(request, cookie) {
|
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
//#endregion
|
|
140
|
-
//#region src/
|
|
162
|
+
//#region src/site-context/site-detection.ts
|
|
141
163
|
/**
|
|
142
164
|
* Detect site reference from cookie.
|
|
143
165
|
*/
|
|
@@ -161,8 +183,9 @@ function getSiteFromIdOrAlias(siteIdentifier, sites) {
|
|
|
161
183
|
async function resolveSite(request, settings) {
|
|
162
184
|
const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;
|
|
163
185
|
const requestUrl = new URL(request.url);
|
|
186
|
+
const basePathOffset = process.env.MRT_ENV_BASE_PATH ? process.env.MRT_ENV_BASE_PATH.split("/").filter(Boolean).length : 0;
|
|
164
187
|
const resolvers = {
|
|
165
|
-
path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex)),
|
|
188
|
+
path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)),
|
|
166
189
|
querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),
|
|
167
190
|
header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),
|
|
168
191
|
cookie: async () => readSiteFromCookie(request, siteCookie)
|
|
@@ -177,7 +200,7 @@ async function resolveSite(request, settings) {
|
|
|
177
200
|
}
|
|
178
201
|
|
|
179
202
|
//#endregion
|
|
180
|
-
//#region src/
|
|
203
|
+
//#region src/site-context/configs.ts
|
|
181
204
|
/**
|
|
182
205
|
* Default site detection configuration
|
|
183
206
|
*/
|
|
@@ -212,9 +235,9 @@ const DEFAULT_LOCALE_DETECTION = {
|
|
|
212
235
|
};
|
|
213
236
|
|
|
214
237
|
//#endregion
|
|
215
|
-
//#region src/
|
|
238
|
+
//#region src/site-context/cookies.ts
|
|
216
239
|
/**
|
|
217
|
-
* Cookie options for
|
|
240
|
+
* Cookie options for site context cookies
|
|
218
241
|
*/
|
|
219
242
|
const COOKIE_OPTIONS = {
|
|
220
243
|
path: "/",
|
|
@@ -226,20 +249,20 @@ const COOKIE_OPTIONS = {
|
|
|
226
249
|
* Creates a cookie instance with the given name.
|
|
227
250
|
*
|
|
228
251
|
* @param name - Cookie name
|
|
229
|
-
* @returns Cookie instance configured with
|
|
252
|
+
* @returns Cookie instance configured with site context options
|
|
230
253
|
*/
|
|
231
|
-
function
|
|
254
|
+
function createSiteContextCookie(name) {
|
|
232
255
|
return createCookie(name, COOKIE_OPTIONS);
|
|
233
256
|
}
|
|
234
257
|
/**
|
|
235
|
-
* WeakMap to pass resolved locale from
|
|
258
|
+
* WeakMap to pass resolved locale from site context middleware to i18next's findLocale.
|
|
236
259
|
* WeakMap allows garbage collection when requests are done.
|
|
237
260
|
* This is necessary because findLocale() only receives the Request object, not the router context.
|
|
238
261
|
*/
|
|
239
262
|
const requestToLocaleMap = /* @__PURE__ */ new WeakMap();
|
|
240
263
|
|
|
241
264
|
//#endregion
|
|
242
|
-
//#region src/
|
|
265
|
+
//#region src/site-context/locale-detection.ts
|
|
243
266
|
/**
|
|
244
267
|
* Read locale from cookie.
|
|
245
268
|
*/
|
|
@@ -270,8 +293,9 @@ async function resolveLocale(request, settings, site) {
|
|
|
270
293
|
const { supportedLocales } = site;
|
|
271
294
|
let locale = null;
|
|
272
295
|
const requestUrl = new URL(request.url);
|
|
296
|
+
const basePathOffset = process.env.MRT_ENV_BASE_PATH ? process.env.MRT_ENV_BASE_PATH.split("/").filter(Boolean).length : 0;
|
|
273
297
|
const resolvers = {
|
|
274
|
-
path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex)),
|
|
298
|
+
path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)),
|
|
275
299
|
querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),
|
|
276
300
|
header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),
|
|
277
301
|
cookie: async () => readLocaleFromCookie(request, localeCookie)
|
|
@@ -286,10 +310,10 @@ async function resolveLocale(request, settings, site) {
|
|
|
286
310
|
}
|
|
287
311
|
|
|
288
312
|
//#endregion
|
|
289
|
-
//#region src/
|
|
290
|
-
const
|
|
313
|
+
//#region src/site-context/middleware.ts
|
|
314
|
+
const siteContext = createContext$1(null);
|
|
291
315
|
/**
|
|
292
|
-
* Helper function to get
|
|
316
|
+
* Helper function to get site context cookies from router context.
|
|
293
317
|
* Useful in server actions and loaders that need to read/set cookies.
|
|
294
318
|
*
|
|
295
319
|
* @param context - Router context provider
|
|
@@ -298,7 +322,7 @@ const multiSiteContext = createContext$1(null);
|
|
|
298
322
|
* @example
|
|
299
323
|
* ```typescript
|
|
300
324
|
* export const action: ActionFunction = async ({ request, context }) => {
|
|
301
|
-
* const cookies =
|
|
325
|
+
* const cookies = getSiteContextCookies(context);
|
|
302
326
|
* if (cookies) {
|
|
303
327
|
* const cookieHeader = await cookies.localeCookie.serialize(locale);
|
|
304
328
|
* // ... use cookieHeader
|
|
@@ -306,50 +330,52 @@ const multiSiteContext = createContext$1(null);
|
|
|
306
330
|
* };
|
|
307
331
|
* ```
|
|
308
332
|
*/
|
|
309
|
-
function
|
|
310
|
-
const
|
|
311
|
-
if (!
|
|
333
|
+
function getSiteContextCookies(context) {
|
|
334
|
+
const siteCtx = context.get(siteContext);
|
|
335
|
+
if (!siteCtx) return null;
|
|
312
336
|
return {
|
|
313
|
-
siteCookie:
|
|
314
|
-
localeCookie:
|
|
337
|
+
siteCookie: siteCtx.siteCookie,
|
|
338
|
+
localeCookie: siteCtx.localeCookie
|
|
315
339
|
};
|
|
316
340
|
}
|
|
317
341
|
/**
|
|
318
342
|
* Helper function to determine if cookies should be set based on:
|
|
319
343
|
* 1. Whether caching is enabled for each cookie type
|
|
320
|
-
* 2. Whether
|
|
344
|
+
* 2. Whether the resolved value differs from the existing cookie
|
|
321
345
|
* 3. Whether cookies were already set by actions/loaders in the response
|
|
322
346
|
*
|
|
323
347
|
* @param request - Incoming request
|
|
324
348
|
* @param response - Response from next()
|
|
325
|
-
* @param settings -
|
|
349
|
+
* @param settings - Site context settings with cookie instances and detection config
|
|
350
|
+
* @param site - Resolved site for this request
|
|
351
|
+
* @param locale - Resolved locale for this request
|
|
326
352
|
* @returns Object with shouldSetSiteCookie and shouldSetLocaleCookie booleans
|
|
327
353
|
*/
|
|
328
|
-
async function shouldSetCookies(request, response, settings) {
|
|
354
|
+
async function shouldSetCookies(request, response, settings, site, locale) {
|
|
329
355
|
const cacheSite = settings.siteDetectionConfig.caches?.includes("cookie");
|
|
330
356
|
const cacheLocale = settings.localeDetectionConfig.caches?.includes("cookie");
|
|
331
357
|
if (!cacheSite && !cacheLocale) return {
|
|
332
358
|
shouldSetSiteCookie: false,
|
|
333
359
|
shouldSetLocaleCookie: false
|
|
334
360
|
};
|
|
335
|
-
const requestCookieHeader = request.headers.get("Cookie");
|
|
336
|
-
const [existingSiteCookie, existingLocaleCookie] = await Promise.all([settings.siteCookie.parse(requestCookieHeader), settings.localeCookie.parse(requestCookieHeader)]);
|
|
337
361
|
const responseSetCookies = response.headers.getSetCookie?.() || [];
|
|
338
362
|
const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) => cookie.startsWith(`${settings.siteCookie.name}=`));
|
|
339
363
|
const isSettingLocaleCookieInResponse = responseSetCookies.some((cookie) => cookie.startsWith(`${settings.localeCookie.name}=`));
|
|
364
|
+
const requestCookieHeader = request.headers.get("Cookie");
|
|
365
|
+
const [existingSiteCookie, existingLocaleCookie] = await Promise.all([settings.siteCookie.parse(requestCookieHeader), settings.localeCookie.parse(requestCookieHeader)]);
|
|
340
366
|
return {
|
|
341
|
-
shouldSetSiteCookie: cacheSite && !
|
|
342
|
-
shouldSetLocaleCookie: cacheLocale && !
|
|
367
|
+
shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,
|
|
368
|
+
shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id
|
|
343
369
|
};
|
|
344
370
|
}
|
|
345
371
|
/**
|
|
346
|
-
* Creates a
|
|
372
|
+
* Creates a site context middleware that resolves the current site from
|
|
347
373
|
* the request (path, cookie, header, query, or default) and stores the
|
|
348
374
|
* result in the router context.
|
|
349
375
|
*
|
|
350
376
|
* Does not import or read from app config context; the consumer supplies config.
|
|
351
377
|
*/
|
|
352
|
-
function
|
|
378
|
+
function createSiteContextMiddleware(config) {
|
|
353
379
|
const siteDetectionConfig = {
|
|
354
380
|
...DEFAULT_SITE_DETECTION,
|
|
355
381
|
...config.siteDetectionConfig
|
|
@@ -358,8 +384,8 @@ function createMultiSiteMiddleware(config) {
|
|
|
358
384
|
...DEFAULT_LOCALE_DETECTION,
|
|
359
385
|
...config.localeDetectionConfig
|
|
360
386
|
};
|
|
361
|
-
const siteCookie =
|
|
362
|
-
const localeCookie =
|
|
387
|
+
const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie);
|
|
388
|
+
const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie);
|
|
363
389
|
const settings = {
|
|
364
390
|
...config,
|
|
365
391
|
siteDetectionConfig,
|
|
@@ -367,10 +393,10 @@ function createMultiSiteMiddleware(config) {
|
|
|
367
393
|
siteCookie,
|
|
368
394
|
localeCookie
|
|
369
395
|
};
|
|
370
|
-
const
|
|
396
|
+
const siteContextMiddleware = async ({ request, context }, next) => {
|
|
371
397
|
const site = await resolveSite(request, settings);
|
|
372
398
|
const locale = await resolveLocale(request, settings, site);
|
|
373
|
-
context.set(
|
|
399
|
+
context.set(siteContext, {
|
|
374
400
|
site,
|
|
375
401
|
locale,
|
|
376
402
|
siteCookie: settings.siteCookie,
|
|
@@ -378,16 +404,16 @@ function createMultiSiteMiddleware(config) {
|
|
|
378
404
|
});
|
|
379
405
|
requestToLocaleMap.set(request, locale.id);
|
|
380
406
|
const response = await next();
|
|
381
|
-
const { shouldSetSiteCookie, shouldSetLocaleCookie } = await shouldSetCookies(request, response, settings);
|
|
407
|
+
const { shouldSetSiteCookie, shouldSetLocaleCookie } = await shouldSetCookies(request, response, settings, site, locale);
|
|
382
408
|
if (!shouldSetSiteCookie && !shouldSetLocaleCookie) return response;
|
|
383
409
|
const [siteSetCookie, localeSetCookie] = await Promise.all([shouldSetSiteCookie ? settings.siteCookie.serialize(site.id, { path: "/" }) : Promise.resolve(null), shouldSetLocaleCookie ? settings.localeCookie.serialize(locale.id, { path: "/" }) : Promise.resolve(null)]);
|
|
384
410
|
if (siteSetCookie) response.headers.append("Set-Cookie", siteSetCookie);
|
|
385
411
|
if (localeSetCookie) response.headers.append("Set-Cookie", localeSetCookie);
|
|
386
412
|
return response;
|
|
387
413
|
};
|
|
388
|
-
return
|
|
414
|
+
return siteContextMiddleware;
|
|
389
415
|
}
|
|
390
416
|
|
|
391
417
|
//#endregion
|
|
392
|
-
export { SiteProvider, applyUrlConfig, buildUrl,
|
|
393
|
-
//# sourceMappingURL=
|
|
418
|
+
export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
|
|
419
|
+
//# sourceMappingURL=site-context.js.map
|