@salesforce/storefront-next-runtime 0.4.1 → 1.0.0-alpha.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 +9 -3
- package/dist/config.d.ts +33 -221
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -116
- package/dist/config.js.map +1 -1
- package/dist/data-store.d.ts +185 -15
- package/dist/data-store.d.ts.map +1 -1
- package/dist/data-store.js +412 -10
- package/dist/data-store.js.map +1 -1
- package/dist/design-data.d.ts +266 -62
- package/dist/design-data.d.ts.map +1 -1
- package/dist/design-data.js +399 -14
- package/dist/design-data.js.map +1 -1
- package/dist/design-mode.d.ts +3 -2
- package/dist/design-mode.d.ts.map +1 -1
- package/dist/events.d.ts +32 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/i18n-client.d.ts.map +1 -1
- package/dist/i18n-client.js.map +1 -1
- package/dist/i18n.d.ts +1 -2
- package/dist/i18n.d.ts.map +1 -1
- package/dist/modeDetection.js +0 -18
- package/dist/modeDetection.js.map +1 -1
- package/dist/scapi.d.ts +2185 -466
- 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 +17 -15
- package/dist/schema.d.ts.map +1 -1
- package/dist/site-context.d.ts +43 -27
- package/dist/site-context.d.ts.map +1 -1
- package/dist/site-context.js +2 -2
- package/dist/site-context2.js +41 -31
- package/dist/site-context2.js.map +1 -1
- package/dist/types.d.ts +19 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types2.d.ts +89 -63
- package/dist/types2.d.ts.map +1 -1
- package/package.json +2 -20
- package/dist/custom-global-preferences.d.ts +0 -20
- package/dist/custom-global-preferences.d.ts.map +0 -1
- package/dist/custom-global-preferences.js +0 -31
- package/dist/custom-global-preferences.js.map +0 -1
- package/dist/custom-site-preferences.d.ts +0 -20
- package/dist/custom-site-preferences.d.ts.map +0 -1
- package/dist/custom-site-preferences.js +0 -31
- package/dist/custom-site-preferences.js.map +0 -1
- package/dist/data-store-custom-global-preferences.d.ts +0 -2
- package/dist/data-store-custom-global-preferences.js +0 -6
- package/dist/data-store-custom-site-preferences.d.ts +0 -2
- package/dist/data-store-custom-site-preferences.js +0 -6
- package/dist/data-store-gcp-preferences.d.ts +0 -2
- package/dist/data-store-gcp-preferences.js +0 -6
- package/dist/gcp-preferences.d.ts +0 -52
- package/dist/gcp-preferences.d.ts.map +0 -1
- package/dist/gcp-preferences.js +0 -64
- package/dist/gcp-preferences.js.map +0 -1
- package/dist/utils.js +0 -90
- package/dist/utils.js.map +0 -1
package/dist/schema.d.ts
CHANGED
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Generic parameter `App` represents the template's application config shape.
|
|
6
6
|
* The SDK does not prescribe what fields `app` must contain — templates define
|
|
7
|
-
* their own `AppConfig` type
|
|
8
|
-
* and pass it as `BaseConfig<AppConfig>`.
|
|
7
|
+
* their own `AppConfig` type and pass it as `BaseConfig<AppConfig>`.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Validation of `app` (e.g., required credentials, required collections) is
|
|
10
|
+
* the template's responsibility, typically handled in its server middleware.
|
|
12
11
|
*
|
|
13
12
|
* @typeParam App - The template's application config shape (defaults to `Record<string, unknown>`)
|
|
14
13
|
*
|
|
15
14
|
* @example
|
|
16
15
|
* // In the template's types file:
|
|
17
|
-
* type AppConfig = {
|
|
16
|
+
* type AppConfig = { ... };
|
|
18
17
|
* type Config = BaseConfig<AppConfig>;
|
|
19
18
|
*
|
|
20
19
|
* // In config.server.ts:
|
|
@@ -38,23 +37,27 @@ interface DefineConfigOptions {
|
|
|
38
37
|
/**
|
|
39
38
|
* Config paths that cannot be overridden by environment variables.
|
|
40
39
|
* Paths use double underscore separators and are matched case-insensitively.
|
|
40
|
+
* Any env var targeting a protected path or a sub-path of it will throw.
|
|
41
41
|
*
|
|
42
|
-
* @example ['
|
|
42
|
+
* @example ['app__analytics'] — prevents PUBLIC__app__analytics__* from being set via env
|
|
43
43
|
*/
|
|
44
44
|
protectedPaths?: string[];
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Define a type-safe storefront configuration with IDE autocomplete.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* Reads `process.env` at call time and merges any `PUBLIC__`-prefixed
|
|
50
|
+
* variables into the config (validated against the base config structure —
|
|
51
|
+
* env vars targeting paths that don't exist in the base config are ignored
|
|
52
|
+
* with a warning). This is a server-only side effect by design; calling
|
|
53
|
+
* `defineConfig` from a browser bundle silently no-ops because `PUBLIC__`
|
|
54
|
+
* vars are not present in the client environment.
|
|
52
55
|
*
|
|
53
56
|
* Environment variables:
|
|
54
57
|
* - `PUBLIC__<path>` (optional): Override any config path using double underscore separators.
|
|
55
|
-
* e.g. `
|
|
56
|
-
* -
|
|
57
|
-
*
|
|
58
|
+
* e.g. `PUBLIC__app__some__nested__value=abc123` maps to `config.app.some.nested.value`
|
|
59
|
+
* - JSON values are parsed optimistically: numbers, booleans, arrays, and objects all work.
|
|
60
|
+
* `PUBLIC__app__features__providers=["A","B"]` parses to an array.
|
|
58
61
|
*
|
|
59
62
|
* @param config - The base configuration object with all defaults
|
|
60
63
|
* @param options - Optional settings (e.g., protectedPaths to prevent env var overrides)
|
|
@@ -67,10 +70,9 @@ interface DefineConfigOptions {
|
|
|
67
70
|
* export default defineConfig({
|
|
68
71
|
* metadata: { projectName: 'My Store', projectSlug: 'my-store' },
|
|
69
72
|
* app: {
|
|
70
|
-
*
|
|
71
|
-
* defaultSiteId: 'RefArch',
|
|
73
|
+
* // template-specific shape
|
|
72
74
|
* },
|
|
73
|
-
* }, { protectedPaths: ['
|
|
75
|
+
* }, { protectedPaths: ['app__analytics'] });
|
|
74
76
|
*/
|
|
75
77
|
declare function defineConfig<T extends BaseConfig>(config: T, options?: DefineConfigOptions): T;
|
|
76
78
|
//#endregion
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":[],"mappings":";;AAqCA;;;;;;AAeA;AA0CA;;;;;;;;;;;;KAzDY,uBAAuB,0BAA0B;;;;;;;;;;oBAUrC;;OAEf;;UAGQ,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CD,uBAAuB,oBAAoB,aAAa,sBAAsB"}
|
package/dist/site-context.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
4
|
-
import * as
|
|
3
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
4
|
+
import * as react_router10 from "react-router";
|
|
5
5
|
import { Cookie, CookieOptions, MiddlewareFunction, RouterContextProvider } from "react-router";
|
|
6
6
|
import { RouteConfigEntry } from "@react-router/dev/routes";
|
|
7
7
|
|
|
@@ -102,7 +102,7 @@ declare function SiteProvider({
|
|
|
102
102
|
language,
|
|
103
103
|
currency,
|
|
104
104
|
children
|
|
105
|
-
}: PropsWithChildren<SiteContextValue>):
|
|
105
|
+
}: PropsWithChildren<SiteContextValue>): react_jsx_runtime2.JSX.Element;
|
|
106
106
|
/**
|
|
107
107
|
* React hook to get the current site context.
|
|
108
108
|
* Returns `{ site, locale, language, currency }`.
|
|
@@ -134,31 +134,47 @@ declare function applyUrlConfig(options: {
|
|
|
134
134
|
//#region src/site-context/build-url.d.ts
|
|
135
135
|
/**
|
|
136
136
|
* Resolves a prefix template by replacing parameter placeholders with values.
|
|
137
|
-
*
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* resolvePrefix({ prefix: '/:siteId/:localeId', params: { siteId: 'global', localeId: 'en-GB' } })
|
|
140
|
+
* // → '/global/en-GB'
|
|
138
141
|
*/
|
|
139
|
-
declare function resolvePrefix(
|
|
142
|
+
declare function resolvePrefix({
|
|
143
|
+
prefix,
|
|
144
|
+
params
|
|
145
|
+
}: {
|
|
146
|
+
prefix: string;
|
|
147
|
+
params: Record<string, string>;
|
|
148
|
+
}): string;
|
|
140
149
|
/**
|
|
141
|
-
* Strips
|
|
142
|
-
* Since all routes are configured with the prefix baked in, segment counting is sufficient.
|
|
150
|
+
* Strips a URL prefix from a pathname.
|
|
143
151
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
152
|
+
* Accepts either a resolved prefix or a prefix pattern — segments may be
|
|
153
|
+
* literal strings (must match the pathname exactly) or `:param` placeholders
|
|
154
|
+
* (match any segment value). Mixed prefixes are supported.
|
|
155
|
+
*
|
|
156
|
+
* Returns `''` when the pathname matches the prefix exactly with no remainder
|
|
157
|
+
* (so concatenating `prefix + result` round-trips the input), `pathname`
|
|
158
|
+
* unchanged when literal segments don't match or the path is shorter than the
|
|
159
|
+
* prefix, or the bare remainder otherwise. Callers that need the homepage to
|
|
160
|
+
* be `'/'` should coerce: `stripPathPrefix(...) || '/'`.
|
|
148
161
|
*
|
|
149
162
|
* @example
|
|
150
|
-
* stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId')
|
|
151
|
-
* stripPathPrefix('/checkout', '
|
|
152
|
-
* stripPathPrefix('/
|
|
153
|
-
* stripPathPrefix('/', '/:siteId/:localeId')
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
* Sanitize a resolved prefix from a pathname if present.
|
|
158
|
-
* sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'
|
|
159
|
-
* sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)
|
|
163
|
+
* stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'
|
|
164
|
+
* stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/global/en-GB' }) // → '/checkout'
|
|
165
|
+
* stripPathPrefix({ pathname: '/shop/en-GB/x', prefix: '/shop/:localeId' }) // → '/x'
|
|
166
|
+
* stripPathPrefix({ pathname: '/global/en-GB', prefix: '/:siteId/:localeId' }) // → ''
|
|
167
|
+
* stripPathPrefix({ pathname: '/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'
|
|
168
|
+
* stripPathPrefix({ pathname: '/other/x', prefix: '/global/en-GB' }) // → '/other/x'
|
|
169
|
+
* stripPathPrefix({ pathname: '/x', prefix: '' }) // → '/x'
|
|
160
170
|
*/
|
|
161
|
-
declare function
|
|
171
|
+
declare function stripPathPrefix({
|
|
172
|
+
pathname,
|
|
173
|
+
prefix
|
|
174
|
+
}: {
|
|
175
|
+
pathname: string;
|
|
176
|
+
prefix: string;
|
|
177
|
+
}): string;
|
|
162
178
|
/**
|
|
163
179
|
* Builds a fully-qualified URL with site context prefix and search params.
|
|
164
180
|
*
|
|
@@ -182,7 +198,7 @@ declare function buildUrl({
|
|
|
182
198
|
}): string;
|
|
183
199
|
//#endregion
|
|
184
200
|
//#region src/site-context/middleware.d.ts
|
|
185
|
-
declare const siteContext:
|
|
201
|
+
declare const siteContext: react_router10.RouterContext<SiteContext | null>;
|
|
186
202
|
/**
|
|
187
203
|
* Resolved site context result from {@link resolveSiteContext}.
|
|
188
204
|
*/
|
|
@@ -230,9 +246,9 @@ declare function resolveSiteContext(request: Request, settings: SiteSettings): P
|
|
|
230
246
|
* ```
|
|
231
247
|
*/
|
|
232
248
|
declare function getSiteContextCookies(context: Readonly<RouterContextProvider>): {
|
|
233
|
-
siteCookie:
|
|
234
|
-
localeCookie:
|
|
235
|
-
currencyCookie:
|
|
249
|
+
siteCookie: react_router10.Cookie;
|
|
250
|
+
localeCookie: react_router10.Cookie;
|
|
251
|
+
currencyCookie: react_router10.Cookie;
|
|
236
252
|
} | null;
|
|
237
253
|
/**
|
|
238
254
|
* Creates a site context middleware that resolves the current site from
|
|
@@ -251,5 +267,5 @@ declare function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunc
|
|
|
251
267
|
*/
|
|
252
268
|
declare const requestToLocaleMap: WeakMap<Request, string>;
|
|
253
269
|
//#endregion
|
|
254
|
-
export { type DetectionConfig, type Locale, type ResolvedSiteContext, type Site, type SiteConfig, type SiteContext, type SiteContextValue, SiteProvider, type SiteSettings, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, resolveSiteContext,
|
|
270
|
+
export { type DetectionConfig, type Locale, type ResolvedSiteContext, type Site, type SiteConfig, type SiteContext, type SiteContextValue, SiteProvider, type SiteSettings, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, resolveSiteContext, siteContext, stripPathPrefix, useSite };
|
|
255
271
|
//# sourceMappingURL=site-context.d.ts.map
|
|
@@ -1 +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":";;;;;;;;;AAkCgB,KAdJ,MAAA,GAAS,QAcL,GAAA;EACE,KAAA,CAAA,EAAA,MAAA;CACE;AAAM,KAZd,IAAA,GAAO,IAYO,CAZF,MAYE,EAAA,kBAAA,CAAA,GAAA;EAOd,IAAA,CAAA,EAAA,MAAU;EACX,KAAA,CAAA,EAAA,MAAA;EAGe,gBAAA,EApBJ,MAoBI,EAAA;CACE;AAER,KApBR,WAAA,GAoBQ;EAAa,IAAA,EAnBvB,IAmBuB;EAMrB,MAAA,EAxBA,MAwBA;EAAe,QAAA,EAAA,MAAA;EACO,UAAA,EAvBlB,MAuBkB;EAAT,YAAA,EAtBP,MAsBO;EACW,cAAA,EAtBhB,MAsBgB;CAAT;;;;;AAOf,KAtBA,UAAA,GAsBe;EAGf,KAAA,EAxBD,IAwBC,EAAA;;;wBArBc;EC1Bd,qBAAgB,CAAA,ED2BA,eCdhB;EA4BI,kBAAY,CAAA,EAAA,MAAA;EAAG,aAAA,CAAA,EDZX,aCYW;CAAM;;;;AAA0D,KDNnF,YAAA,GAAe,UCMoE,GAAA;EAAlB,mBAAA,EDLpD,QCKoD,CDL3C,eCK2C,CAAA;EAAmC,qBAAA,EDJrF,QCIqF,CDJ5E,eCI4E,CAAA;EAAA,UAAA,EDHhG,MCGgG;EAWhG,YAAO,EDbL,MCaK;kBDZH;;;AE2DJ,KFvDJ,eAAA,GEuDkB,MAAA,GAAA,aAAA,GAAA,QAAA,GAAA,QAAA;AAClB,KFrDA,eAAA,GEqDA;EACI,KAAA,EFrDL,eEqDK,EAAA;EAEZ,mBAAA,CAAA,EAAA,MAAA;EAAgB,iBAAA,CAAA,EAAA,MAAA;;;WFlDP;
|
|
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":"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"}
|
package/dist/site-context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as requestToLocaleMap, c as
|
|
1
|
+
import { a as requestToLocaleMap, c as stripPathPrefix, i as siteContext, l as SiteProvider, n as getSiteContextCookies, o as buildUrl, r as resolveSiteContext, s as resolvePrefix, t as createSiteContextMiddleware, u as useSite } from "./site-context2.js";
|
|
2
2
|
import { t as applyUrlConfig } from "./apply-url-config.js";
|
|
3
3
|
|
|
4
|
-
export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, resolveSiteContext,
|
|
4
|
+
export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, resolveSiteContext, siteContext, stripPathPrefix, useSite };
|
package/dist/site-context2.js
CHANGED
|
@@ -77,9 +77,12 @@ function decomposeUrl(url) {
|
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* Resolves a prefix template by replacing parameter placeholders with values.
|
|
80
|
-
*
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* resolvePrefix({ prefix: '/:siteId/:localeId', params: { siteId: 'global', localeId: 'en-GB' } })
|
|
83
|
+
* // → '/global/en-GB'
|
|
81
84
|
*/
|
|
82
|
-
function resolvePrefix(prefix, params) {
|
|
85
|
+
function resolvePrefix({ prefix, params }) {
|
|
83
86
|
let resolved = prefix;
|
|
84
87
|
for (const paramName of extractPrefixParams(prefix)) {
|
|
85
88
|
const value = params[paramName];
|
|
@@ -88,37 +91,38 @@ function resolvePrefix(prefix, params) {
|
|
|
88
91
|
return resolved;
|
|
89
92
|
}
|
|
90
93
|
/**
|
|
91
|
-
* Strips
|
|
92
|
-
* Since all routes are configured with the prefix baked in, segment counting is sufficient.
|
|
94
|
+
* Strips a URL prefix from a pathname.
|
|
93
95
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
96
|
+
* Accepts either a resolved prefix or a prefix pattern — segments may be
|
|
97
|
+
* literal strings (must match the pathname exactly) or `:param` placeholders
|
|
98
|
+
* (match any segment value). Mixed prefixes are supported.
|
|
99
|
+
*
|
|
100
|
+
* Returns `''` when the pathname matches the prefix exactly with no remainder
|
|
101
|
+
* (so concatenating `prefix + result` round-trips the input), `pathname`
|
|
102
|
+
* unchanged when literal segments don't match or the path is shorter than the
|
|
103
|
+
* prefix, or the bare remainder otherwise. Callers that need the homepage to
|
|
104
|
+
* be `'/'` should coerce: `stripPathPrefix(...) || '/'`.
|
|
98
105
|
*
|
|
99
106
|
* @example
|
|
100
|
-
* stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId')
|
|
101
|
-
* stripPathPrefix('/checkout', '
|
|
102
|
-
* stripPathPrefix('/
|
|
103
|
-
* stripPathPrefix('/', '/:siteId/:localeId')
|
|
107
|
+
* stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'
|
|
108
|
+
* stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/global/en-GB' }) // → '/checkout'
|
|
109
|
+
* stripPathPrefix({ pathname: '/shop/en-GB/x', prefix: '/shop/:localeId' }) // → '/x'
|
|
110
|
+
* stripPathPrefix({ pathname: '/global/en-GB', prefix: '/:siteId/:localeId' }) // → ''
|
|
111
|
+
* stripPathPrefix({ pathname: '/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'
|
|
112
|
+
* stripPathPrefix({ pathname: '/other/x', prefix: '/global/en-GB' }) // → '/other/x'
|
|
113
|
+
* stripPathPrefix({ pathname: '/x', prefix: '' }) // → '/x'
|
|
104
114
|
*/
|
|
105
|
-
function stripPathPrefix(pathname,
|
|
106
|
-
if (!
|
|
107
|
-
const
|
|
115
|
+
function stripPathPrefix({ pathname, prefix }) {
|
|
116
|
+
if (!prefix || prefix === "/") return pathname;
|
|
117
|
+
const prefixSegments = prefix.split("/").filter(Boolean);
|
|
108
118
|
const pathSegments = pathname.split("/").filter(Boolean);
|
|
109
|
-
if (pathSegments.length
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*/
|
|
117
|
-
function sanitizePrefix(pathname, pathPrefix) {
|
|
118
|
-
if (!pathPrefix) return pathname;
|
|
119
|
-
if (pathname === pathPrefix) return "";
|
|
120
|
-
if (pathname.startsWith(`${pathPrefix}/`)) return pathname.slice(pathPrefix.length);
|
|
121
|
-
return pathname;
|
|
119
|
+
if (pathSegments.length < prefixSegments.length) return pathname;
|
|
120
|
+
for (let i = 0; i < prefixSegments.length; i++) {
|
|
121
|
+
const segment = prefixSegments[i];
|
|
122
|
+
if (!segment.startsWith(":") && segment !== pathSegments[i]) return pathname;
|
|
123
|
+
}
|
|
124
|
+
const remaining = pathSegments.slice(prefixSegments.length);
|
|
125
|
+
return remaining.length === 0 ? "" : `/${remaining.join("/")}`;
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
124
128
|
* Builds a fully-qualified URL with site context prefix and search params.
|
|
@@ -136,8 +140,14 @@ function buildUrl({ to, urlConfig, params }) {
|
|
|
136
140
|
if (!urlConfig) return to;
|
|
137
141
|
if (!to || to === "#" || to.startsWith("http") || to.startsWith("//")) return to;
|
|
138
142
|
const { pathname, search: existingSearch, hash } = decomposeUrl(to);
|
|
139
|
-
const pathPrefix = urlConfig.prefix && urlConfig.prefix !== "/" ? resolvePrefix(
|
|
140
|
-
|
|
143
|
+
const pathPrefix = urlConfig.prefix && urlConfig.prefix !== "/" ? resolvePrefix({
|
|
144
|
+
prefix: urlConfig.prefix,
|
|
145
|
+
params
|
|
146
|
+
}) : "";
|
|
147
|
+
const path = pathPrefix ? `${pathPrefix}${stripPathPrefix({
|
|
148
|
+
pathname,
|
|
149
|
+
prefix: pathPrefix
|
|
150
|
+
})}` : pathname;
|
|
141
151
|
const searchParams = new URLSearchParams(existingSearch);
|
|
142
152
|
if (urlConfig.search) {
|
|
143
153
|
const searchConfig = parseSearchConfig(urlConfig.search);
|
|
@@ -509,5 +519,5 @@ function createSiteContextMiddleware(config) {
|
|
|
509
519
|
}
|
|
510
520
|
|
|
511
521
|
//#endregion
|
|
512
|
-
export { requestToLocaleMap as a,
|
|
522
|
+
export { requestToLocaleMap as a, stripPathPrefix as c, siteContext as i, SiteProvider as l, getSiteContextCookies as n, buildUrl as o, resolveSiteContext as r, resolvePrefix as s, createSiteContextMiddleware as t, useSite as u };
|
|
513
523
|
//# sourceMappingURL=site-context2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-context2.js","names":["result: Record<string, string>","resolvers: Record<DetectionMethod, () => Promise<string | null>>","DEFAULT_SITE_DETECTION: Required<DetectionConfig>","DEFAULT_LOCALE_DETECTION: Required<DetectionConfig>","locale: Locale | null","resolvers: Record<DetectionMethod, () => Promise<string | null>>","createContext","siteDetectionConfig: SiteSettings['siteDetectionConfig']","localeDetectionConfig: SiteSettings['localeDetectionConfig']","settings: SiteSettings","siteContextMiddleware: MiddlewareFunction<Response>"],"sources":["../src/site-context/site-context.tsx","../src/site-context/build-url.ts","../src/site-context/utils.ts","../src/site-context/site-detection.ts","../src/site-context/configs.ts","../src/site-context/cookies.ts","../src/site-context/locale-detection.ts","../src/site-context/currency-detection.ts","../src/site-context/middleware.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createContext, useContext, useMemo, type PropsWithChildren } from 'react';\nimport type { Locale, Site } from './types';\n\n/**\n * The value provided by {@link SiteProvider} and returned by {@link useSite}.\n */\nexport type SiteContextValue = {\n /**\n * The resolved site configuration object for the current request.\n * Contains the site's supported locales, supported currencies, and default values.\n */\n site: Site;\n\n /**\n * The full locale object from the site's `supportedLocales` list for the current request.\n * Contains structured locale metadata: `id` (e.g. `\"en-GB\"`), optional `alias` (e.g. `\"en\"`),\n * and optional `preferredCurrency`.\n *\n */\n locale: Locale;\n\n /**\n * The current i18next language string (e.g. `\"en-GB\"`, `\"fr-FR\"`).\n * This is the value returned by `i18next.language` and drives which translation\n * namespace is active. Passed as a prop because the SDK has no react-i18next dependency.\n *\n * @see {@link SiteContextValue.locale} for the full locale object from site config.\n */\n language: string;\n\n /**\n * The active currency code for the current session (e.g. `\"USD\"`, `\"GBP\"`).\n * Resolved from the locale's `preferredCurrency`, a currency cookie, or the site's\n * `defaultCurrency`.\n */\n currency: string;\n};\n\nconst SiteContext = createContext<SiteContextValue | undefined>(undefined);\n\n/**\n * Provides the current site context (site, locale, language, currency) to the component tree.\n *\n * Mounted in the template's root.tsx with the resolved values from the\n * loader/middleware. The SDK has no react-i18next dependency, so `language`\n * is passed as a prop from the template.\n */\nexport function SiteProvider({ site, locale, language, currency, children }: PropsWithChildren<SiteContextValue>) {\n const value = useMemo(() => ({ site, locale, language, currency }), [site, locale, language, currency]);\n return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>;\n}\n\n/**\n * React hook to get the current site context.\n * Returns `{ site, locale, language, currency }`.\n * @throws If called outside of a SiteProvider\n */\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useSite(): SiteContextValue {\n const value = useContext(SiteContext);\n if (!value) {\n throw new Error('useSite must be used within a SiteProvider');\n }\n return value;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Url } from '../config/types';\n\n/**\n * Parses search config string into key-value pairs, preserving ':param' placeholders.\n * '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }\n */\nexport function parseSearchConfig(search: string): Record<string, string> {\n const searchParams = new URLSearchParams(search);\n const result: Record<string, string> = {};\n for (const [key, value] of searchParams) {\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Extracts parameter names from a prefix string.\n * '/:siteId/:localeId' → ['siteId', 'localeId']\n */\nexport function extractPrefixParams(prefix: string): string[] {\n const matches = prefix.match(/:(\\w+)/g);\n return matches ? matches.map((m) => m.slice(1)) : [];\n}\n\n/**\n * Splits a URL string into its component parts.\n * '/product/123?color=red#details' → { pathname: '/product/123', search: 'color=red', hash: '#details' }\n */\nexport function decomposeUrl(url: string): { pathname: string; search: string; hash: string } {\n const hashIdx = url.indexOf('#');\n const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';\n const withoutHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;\n const searchIdx = withoutHash.indexOf('?');\n const search = searchIdx >= 0 ? withoutHash.slice(searchIdx + 1) : '';\n const pathname = searchIdx >= 0 ? withoutHash.slice(0, searchIdx) : withoutHash;\n return { pathname, search, hash };\n}\n\n/**\n * Resolves a prefix template by replacing parameter placeholders with values.\n * ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'\n */\nexport function resolvePrefix(prefix: string, params: Record<string, string>): string {\n let resolved = prefix;\n for (const paramName of extractPrefixParams(prefix)) {\n const value = params[paramName];\n if (value) {\n resolved = resolved.replace(`:${paramName}`, value);\n }\n }\n return resolved;\n}\n\n/**\n * Strips the URL prefix segments from a pathname based on a prefix pattern.\n * Since all routes are configured with the prefix baked in, segment counting is sufficient.\n *\n * @param pathname - Full pathname (e.g. '/global/en-GB/checkout')\n * @param prefixPattern - URL prefix pattern from config (e.g. '/:siteId/:localeId')\n * @returns Pathname with prefix stripped (e.g. '/checkout'), or original if\n * the pathname has fewer segments than the prefix\n *\n * @example\n * stripPathPrefix('/global/en-GB/checkout', '/:siteId/:localeId') // → '/checkout'\n * stripPathPrefix('/checkout', '/:siteId/:localeId') // → '/checkout' (fewer segments → unchanged)\n * stripPathPrefix('/checkout', '') // → '/checkout' (no prefix configured)\n * stripPathPrefix('/', '/:siteId/:localeId') // → '/'\n */\nexport function stripPathPrefix(pathname: string, prefixPattern: string): string {\n if (!prefixPattern) return pathname;\n\n const prefixSegmentCount = prefixPattern.split('/').filter(Boolean).length;\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= prefixSegmentCount) {\n return pathSegments.length === prefixSegmentCount ? '/' : pathname;\n }\n\n return `/${pathSegments.slice(prefixSegmentCount).join('/')}`;\n}\n\n/**\n * Sanitize a resolved prefix from a pathname if present.\n * sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'\n * sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)\n */\nexport function sanitizePrefix(pathname: string, pathPrefix: string): string {\n if (!pathPrefix) return pathname;\n if (pathname === pathPrefix) return '';\n if (pathname.startsWith(`${pathPrefix}/`)) return pathname.slice(pathPrefix.length);\n return pathname;\n}\n\n/**\n * Builds a fully-qualified URL with site context prefix and search params.\n *\n * Only keys defined in urlConfig.search are set by site context. Any other query params\n * already present on the `to` URL (including duplicate keys) are preserved as-is.\n * e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'\n * → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'\n *\n * @example\n * buildUrl({ to: '/product/123', urlConfig: { prefix: '/:siteId', search: '?lng=:localeId' }, params: { siteId: 'global', localeId: 'en-GB' } })\n * // → '/global/product/123?lng=en-GB'\n */\nexport function buildUrl({\n to,\n urlConfig,\n params,\n}: {\n to: string;\n urlConfig?: Url;\n params: Record<string, string>;\n}): string {\n if (!urlConfig) return to;\n if (!to || to === '#' || to.startsWith('http') || to.startsWith('//')) return to;\n\n const { pathname, search: existingSearch, hash } = decomposeUrl(to);\n\n const pathPrefix = urlConfig.prefix && urlConfig.prefix !== '/' ? resolvePrefix(urlConfig.prefix, params) : '';\n // sanitize prefix to make sure there is no prefix duplication at any case\n const path = pathPrefix ? `${pathPrefix}${sanitizePrefix(pathname, pathPrefix)}` : pathname;\n\n const searchParams = new URLSearchParams(existingSearch);\n if (urlConfig.search) {\n const searchConfig = parseSearchConfig(urlConfig.search);\n for (const [queryKey, value] of Object.entries(searchConfig)) {\n if (value.startsWith(':')) {\n const paramValue = params[value.slice(1)];\n if (paramValue) {\n searchParams.set(queryKey, paramValue);\n }\n } else {\n searchParams.set(queryKey, value);\n }\n }\n }\n\n const search = searchParams.toString();\n return `${path}${search ? `?${search}` : ''}${hash}`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\n\n/**\n * Extract a string value from the URL path segment at the given index.\n */\nexport function lookupFromPath(pathname: string, pathIndex: number): string | null {\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= pathIndex) return null;\n\n return pathSegments[pathIndex];\n}\n\n/**\n * Detect a string value from cookie using the given cookie parser.\n *\n * Returns a promise that resolves to the cookie value.\n */\nexport async function readCookieFromRequest(request: Request, cookie: Cookie): Promise<string | null> {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n const cookieValue = await cookie.parse(cookies);\n return cookieValue;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, SiteSettings, DetectionMethod } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Detect site reference from cookie.\n */\nexport async function readSiteFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get site object using the site id or alias\n * 1. Check siteIdentifier against each site's alias; if matched, return that site.\n * 2. Else check against each site's id; if matched, return that site.\n * 3. If no match, return null.\n */\nfunction getSiteFromIdOrAlias(siteIdentifier: string | null, sites: Site[]): Site | null {\n if (!siteIdentifier) return null;\n return sites.find((site) => site.alias === siteIdentifier || site.id === siteIdentifier) ?? null;\n}\n\n/**\n * Resolve site using the configured detection order.\n * Returns the first valid site from the first source that yields a valid value.\n */\nexport async function resolveSite(request: Request, settings: SiteSettings): Promise<Site> {\n const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;\n\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),\n cookie: async () => readSiteFromCookie(request, siteCookie),\n };\n\n for (const method of siteDetectionConfig.order) {\n const siteIdOrAlias = await resolvers[method]?.();\n const resolvedSite = getSiteFromIdOrAlias(siteIdOrAlias, sites);\n if (resolvedSite) return resolvedSite;\n }\n\n // If no site id was found, use the default site id\n const site = getSiteFromIdOrAlias(defaultSiteId, sites);\n\n // If default site id is invalid, throw an error\n if (!site) {\n throw new Error(`Default site ${defaultSiteId} not found.`);\n }\n\n return site;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { DetectionConfig } from './types';\n\nexport const DEFAULT_CURRENCY_COOKIE_NAME = 'currency';\n\n/**\n * Default site detection configuration\n */\nexport const DEFAULT_SITE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 0,\n lookupQuerystring: 'site',\n lookupCookie: 'site_id',\n lookupHeader: 'X-Site-Id',\n caches: ['cookie'],\n};\n\n/**\n * Default locale detection configuration\n */\nexport const DEFAULT_LOCALE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 1,\n lookupQuerystring: 'lng',\n lookupCookie: 'lng',\n lookupHeader: 'Accept-Language',\n caches: ['cookie'],\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createCookie, type Cookie, type CookieOptions } from 'react-router';\n\n/**\n * Cookie options for site context cookies (site and locale)\n */\nexport const COOKIE_OPTIONS = {\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env.NODE_ENV === 'production',\n httpOnly: true,\n};\n\n/**\n * Creates a cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with site context options\n */\nexport function createSiteContextCookie(name: string, options?: CookieOptions): Cookie {\n return createCookie(name, { ...COOKIE_OPTIONS, ...options });\n}\n\n/**\n * Creates a currency cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with site context cookie options\n */\nexport function createCurrencyCookie(name: string, options?: CookieOptions): Cookie {\n return createCookie(name, { ...COOKIE_OPTIONS, ...options });\n}\n\n/**\n * WeakMap to pass resolved locale from site context middleware to i18next's findLocale.\n * WeakMap allows garbage collection when requests are done.\n * This is necessary because findLocale() only receives the Request object, not the router context.\n */\nexport const requestToLocaleMap = new WeakMap<Request, string>();\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { DetectionMethod, Locale, SiteSettings, Site } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Read locale from cookie.\n */\nexport async function readLocaleFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get locale object using the locale id or alias.\n * 1. Check localeIdOrAlias against each locale's alias; if matched, return that locale.\n * 2. Else check against each locale's id; if matched, return that locale.\n * 3. If no match, return null (caller should use defaultLocale).\n *\n * @param localeIdentifier - The locale id or alias to get the locale from. Null is allowed because this may come from\n * extrenal sources such as cookies, headers, or query parameters.\n * @param locales - The list of locales to search through.\n * @returns The locale object if found, otherwise null.\n */\nfunction getLocaleFromIdOrAlias(localeIdentifier: string | undefined | null, locales: Locale[]): Locale | null {\n if (!localeIdentifier) return null;\n return locales.find((locale) => locale.alias === localeIdentifier || locale.id === localeIdentifier) ?? null;\n}\n\n/**\n * Resolve locale using the configured detection order.\n * Returns the first valid locale from the first source that yields a valid value.\n */\nexport async function resolveLocale(request: Request, settings: SiteSettings, site: Site): Promise<Locale> {\n const { defaultLocale, localeDetectionConfig, localeCookie } = settings;\n const { supportedLocales } = site;\n\n let locale: Locale | null = null;\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),\n cookie: async () => readLocaleFromCookie(request, localeCookie),\n };\n\n for (const method of localeDetectionConfig.order) {\n const localeIdOrAlias = await resolvers[method]?.();\n const resolvedLocale = getLocaleFromIdOrAlias(localeIdOrAlias, supportedLocales);\n if (resolvedLocale) return resolvedLocale;\n }\n\n // If no locale was found, use the default locale\n if (!locale) {\n locale = getLocaleFromIdOrAlias(defaultLocale, supportedLocales);\n }\n\n // If default locale is invalid, throw an error\n if (!locale) {\n throw new Error(\n `Default locale ${defaultLocale} not found in the list of supported locales for site ${site.id}.`\n );\n }\n\n return locale;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, Locale } from './types';\nimport { readCookieFromRequest } from './utils';\n\n/**\n * Resolve the currency for the current request.\n *\n * Priority:\n * 1. Cookie (user-selected currency, if valid for this site)\n * 2. Locale's preferred currency (if valid for this site)\n * 3. Site's default currency\n *\n * @param request - Incoming request\n * @param currencyCookie - Cookie instance for reading the currency cookie\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @returns The resolved currency code\n */\nexport async function resolveCurrency(\n request: Request,\n currencyCookie: Cookie,\n site: Site,\n locale: Locale\n): Promise<string> {\n const { supportedCurrencies, defaultCurrency } = site;\n\n if (!supportedCurrencies || supportedCurrencies.length === 0) {\n throw new Error(`Site \"${site.id}\" must have supportedCurrencies configured.`);\n }\n\n // 1. Try cookie\n const cookieValue = await readCookieFromRequest(request, currencyCookie);\n if (typeof cookieValue === 'string' && supportedCurrencies.includes(cookieValue)) {\n return cookieValue;\n }\n\n // 2. Try locale's preferred currency\n if (locale.preferredCurrency && supportedCurrencies.includes(locale.preferredCurrency)) {\n return locale.preferredCurrency;\n }\n\n // 3. Fall back to site default\n return defaultCurrency;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createContext, type MiddlewareFunction, type RouterContextProvider } from 'react-router';\nimport { resolveSite } from './site-detection';\nimport type { SiteConfig, SiteContext, SiteSettings, Site, Locale } from './types';\nimport { DEFAULT_SITE_DETECTION, DEFAULT_LOCALE_DETECTION, DEFAULT_CURRENCY_COOKIE_NAME } from './configs';\nimport { createSiteContextCookie, createCurrencyCookie, requestToLocaleMap } from './cookies';\nimport { resolveLocale } from './locale-detection';\nimport { resolveCurrency } from './currency-detection';\n\nexport const siteContext = createContext<SiteContext | null>(null);\n\n/**\n * Resolved site context result from {@link resolveSiteContext}.\n */\nexport type ResolvedSiteContext = {\n site: Site;\n locale: Locale;\n currency: string;\n};\n\n/**\n * Resolve site, locale, and currency from a request in one call.\n *\n * This is the recommended public entry point for site-context resolution.\n * It encapsulates the required resolution order (site → locale → currency)\n * so consumers don't need to manage the dependency chain manually.\n *\n * The individual resolvers (`resolveSite`, `resolveLocale`, `resolveCurrency`)\n * are available as advanced utilities for cases that need fine-grained control.\n *\n * @param request - Incoming HTTP request\n * @param settings - Fully resolved site settings (with detection config and cookies)\n * @returns Resolved site, locale, and currency\n *\n * @example\n * ```typescript\n * const { site, locale, currency } = await resolveSiteContext(request, settings);\n * ```\n */\nexport async function resolveSiteContext(request: Request, settings: SiteSettings): Promise<ResolvedSiteContext> {\n const site = await resolveSite(request, settings);\n const locale = await resolveLocale(request, settings, site);\n const currency = await resolveCurrency(request, settings.currencyCookie, site, locale);\n return { site, locale, currency };\n}\n\ntype MiddlewareArgs = { request: Request; context: Readonly<RouterContextProvider> };\n\n/**\n * Helper function to get site context cookies from router context.\n * Useful in server actions and loaders that need to read/set cookies.\n *\n * @param context - Router context provider\n * @returns Object with siteCookie and localeCookie instances, or null if context not set\n *\n * @example\n * ```typescript\n * export const action: ActionFunction = async ({ request, context }) => {\n * const cookies = getSiteContextCookies(context);\n * if (cookies) {\n * const cookieHeader = await cookies.localeCookie.serialize(locale);\n * // ... use cookieHeader\n * }\n * };\n * ```\n */\nexport function getSiteContextCookies(context: Readonly<RouterContextProvider>) {\n const siteCtx = context.get(siteContext);\n if (!siteCtx) return null;\n return {\n siteCookie: siteCtx.siteCookie,\n localeCookie: siteCtx.localeCookie,\n currencyCookie: siteCtx.currencyCookie,\n };\n}\n\n/**\n * Helper function to determine if cookies should be set based on:\n * 1. Whether caching is enabled for each cookie type\n * 2. Whether the resolved value differs from the existing cookie\n * 3. Whether cookies were already set by actions/loaders in the response\n *\n * @param request - Incoming request\n * @param response - Response from next()\n * @param settings - Site context settings with cookie instances and detection config\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @param currency - Resolved currency for this request\n * @returns Object with shouldSetSiteCookie, shouldSetLocaleCookie, and shouldSetCurrencyCookie booleans\n */\nasync function shouldSetCookies(\n request: Request,\n response: Response,\n settings: SiteSettings,\n site: Site,\n locale: Locale,\n currency: string\n): Promise<{ shouldSetSiteCookie: boolean; shouldSetLocaleCookie: boolean; shouldSetCurrencyCookie: boolean }> {\n const cacheSite = settings.siteDetectionConfig.caches?.includes('cookie');\n const cacheLocale = settings.localeDetectionConfig.caches?.includes('cookie');\n\n // Check if cookies were already set by actions/loaders in the response.\n // If they were, we don't want to override them.\n const responseSetCookies = response.headers.getSetCookie?.() || [];\n const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.siteCookie.name}=`)\n );\n const isSettingLocaleCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.localeCookie.name}=`)\n );\n const isSettingCurrencyCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.currencyCookie.name}=`)\n );\n\n const requestCookieHeader = request.headers.get('Cookie');\n const [existingSiteCookie, existingLocaleCookie, existingCurrencyCookie] = await Promise.all([\n settings.siteCookie.parse(requestCookieHeader),\n settings.localeCookie.parse(requestCookieHeader),\n settings.currencyCookie.parse(requestCookieHeader),\n ]);\n\n // Set cookie if: doesn't exist yet OR resolved value differs from existing.\n // Skip if an action/loader already set it in the response.\n return {\n shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,\n shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id,\n shouldSetCurrencyCookie: !isSettingCurrencyCookieInResponse && existingCurrencyCookie !== currency,\n };\n}\n\n/**\n * Creates a site context middleware that resolves the current site from\n * the request (path, cookie, header, query, or default) and stores the\n * result in the router context.\n *\n * Does not import or read from app config context; the consumer supplies config.\n */\nexport function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response> {\n // Merge config with defaults so every detection option has a value\n const siteDetectionConfig: SiteSettings['siteDetectionConfig'] = {\n ...DEFAULT_SITE_DETECTION,\n ...config.siteDetectionConfig,\n };\n const localeDetectionConfig: SiteSettings['localeDetectionConfig'] = {\n ...DEFAULT_LOCALE_DETECTION,\n ...config.localeDetectionConfig,\n };\n\n // Create cookies based on configured names and optional cookie options\n const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie, config.cookieOptions);\n const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie, config.cookieOptions);\n const currencyCookie = createCurrencyCookie(\n config.currencyCookieName ?? DEFAULT_CURRENCY_COOKIE_NAME,\n config.cookieOptions\n );\n\n const settings: SiteSettings = {\n ...config,\n siteDetectionConfig,\n localeDetectionConfig,\n siteCookie,\n localeCookie,\n currencyCookie,\n };\n\n const siteContextMiddleware: MiddlewareFunction<Response> = async (\n { request, context }: MiddlewareArgs,\n next: () => Promise<Response>\n ): Promise<Response> => {\n const { site, locale, currency } = await resolveSiteContext(request, settings);\n\n // Store full Site, Locale, Currency, and Cookie objects in context\n context.set(siteContext, {\n site,\n locale,\n currency,\n siteCookie: settings.siteCookie,\n localeCookie: settings.localeCookie,\n currencyCookie: settings.currencyCookie,\n });\n\n // Store locale in a WeakMap so i18next's findLocale can access it\n // This is necessary because findLocale only receives Request and cannot access the router context\n requestToLocaleMap.set(request, locale.id);\n\n const response = await next();\n\n // Determine if cookies should be set.\n // Actions that change currency (or site/locale) set their cookies directly on the response;\n // shouldSetCookies detects those Set-Cookie headers and skips to avoid overriding.\n const { shouldSetSiteCookie, shouldSetLocaleCookie, shouldSetCurrencyCookie } = await shouldSetCookies(\n request,\n response,\n settings,\n site,\n locale,\n currency\n );\n\n // Early return if no cookies need to be set\n if (!shouldSetSiteCookie && !shouldSetLocaleCookie && !shouldSetCurrencyCookie) {\n return response;\n }\n\n const [siteSetCookie, localeSetCookie, currencySetCookie] = await Promise.all([\n shouldSetSiteCookie ? settings.siteCookie.serialize(site.id, { path: '/' }) : Promise.resolve(null),\n shouldSetLocaleCookie ? settings.localeCookie.serialize(locale.id, { path: '/' }) : Promise.resolve(null),\n shouldSetCurrencyCookie ? settings.currencyCookie.serialize(currency) : Promise.resolve(null),\n ]);\n\n if (siteSetCookie) response.headers.append('Set-Cookie', siteSetCookie);\n if (localeSetCookie) response.headers.append('Set-Cookie', localeSetCookie);\n if (currencySetCookie) response.headers.append('Set-Cookie', currencySetCookie);\n\n return response;\n };\n\n return siteContextMiddleware;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"site-context2.js","names":["result: Record<string, string>","resolvers: Record<DetectionMethod, () => Promise<string | null>>","DEFAULT_SITE_DETECTION: Required<DetectionConfig>","DEFAULT_LOCALE_DETECTION: Required<DetectionConfig>","locale: Locale | null","resolvers: Record<DetectionMethod, () => Promise<string | null>>","createContext","siteDetectionConfig: SiteSettings['siteDetectionConfig']","localeDetectionConfig: SiteSettings['localeDetectionConfig']","settings: SiteSettings","siteContextMiddleware: MiddlewareFunction<Response>"],"sources":["../src/site-context/site-context.tsx","../src/site-context/build-url.ts","../src/site-context/utils.ts","../src/site-context/site-detection.ts","../src/site-context/configs.ts","../src/site-context/cookies.ts","../src/site-context/locale-detection.ts","../src/site-context/currency-detection.ts","../src/site-context/middleware.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createContext, useContext, useMemo, type PropsWithChildren } from 'react';\nimport type { Locale, Site } from './types';\n\n/**\n * The value provided by {@link SiteProvider} and returned by {@link useSite}.\n */\nexport type SiteContextValue = {\n /**\n * The resolved site configuration object for the current request.\n * Contains the site's supported locales, supported currencies, and default values.\n */\n site: Site;\n\n /**\n * The full locale object from the site's `supportedLocales` list for the current request.\n * Contains structured locale metadata: `id` (e.g. `\"en-GB\"`), optional `alias` (e.g. `\"en\"`),\n * and optional `preferredCurrency`.\n *\n */\n locale: Locale;\n\n /**\n * The current i18next language string (e.g. `\"en-GB\"`, `\"fr-FR\"`).\n * This is the value returned by `i18next.language` and drives which translation\n * namespace is active. Passed as a prop because the SDK has no react-i18next dependency.\n *\n * @see {@link SiteContextValue.locale} for the full locale object from site config.\n */\n language: string;\n\n /**\n * The active currency code for the current session (e.g. `\"USD\"`, `\"GBP\"`).\n * Resolved from the locale's `preferredCurrency`, a currency cookie, or the site's\n * `defaultCurrency`.\n */\n currency: string;\n};\n\nconst SiteContext = createContext<SiteContextValue | undefined>(undefined);\n\n/**\n * Provides the current site context (site, locale, language, currency) to the component tree.\n *\n * Mounted in the template's root.tsx with the resolved values from the\n * loader/middleware. The SDK has no react-i18next dependency, so `language`\n * is passed as a prop from the template.\n */\nexport function SiteProvider({ site, locale, language, currency, children }: PropsWithChildren<SiteContextValue>) {\n const value = useMemo(() => ({ site, locale, language, currency }), [site, locale, language, currency]);\n return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>;\n}\n\n/**\n * React hook to get the current site context.\n * Returns `{ site, locale, language, currency }`.\n * @throws If called outside of a SiteProvider\n */\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useSite(): SiteContextValue {\n const value = useContext(SiteContext);\n if (!value) {\n throw new Error('useSite must be used within a SiteProvider');\n }\n return value;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Url } from '../config/types';\n\n/**\n * Parses search config string into key-value pairs, preserving ':param' placeholders.\n * '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }\n */\nexport function parseSearchConfig(search: string): Record<string, string> {\n const searchParams = new URLSearchParams(search);\n const result: Record<string, string> = {};\n for (const [key, value] of searchParams) {\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Extracts parameter names from a prefix string.\n * '/:siteId/:localeId' → ['siteId', 'localeId']\n */\nexport function extractPrefixParams(prefix: string): string[] {\n const matches = prefix.match(/:(\\w+)/g);\n return matches ? matches.map((m) => m.slice(1)) : [];\n}\n\n/**\n * Splits a URL string into its component parts.\n * '/product/123?color=red#details' → { pathname: '/product/123', search: 'color=red', hash: '#details' }\n */\nexport function decomposeUrl(url: string): { pathname: string; search: string; hash: string } {\n const hashIdx = url.indexOf('#');\n const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';\n const withoutHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;\n const searchIdx = withoutHash.indexOf('?');\n const search = searchIdx >= 0 ? withoutHash.slice(searchIdx + 1) : '';\n const pathname = searchIdx >= 0 ? withoutHash.slice(0, searchIdx) : withoutHash;\n return { pathname, search, hash };\n}\n\n/**\n * Resolves a prefix template by replacing parameter placeholders with values.\n *\n * @example\n * resolvePrefix({ prefix: '/:siteId/:localeId', params: { siteId: 'global', localeId: 'en-GB' } })\n * // → '/global/en-GB'\n */\nexport function resolvePrefix({ prefix, params }: { prefix: string; params: Record<string, string> }): string {\n let resolved = prefix;\n for (const paramName of extractPrefixParams(prefix)) {\n const value = params[paramName];\n if (value) {\n resolved = resolved.replace(`:${paramName}`, value);\n }\n }\n return resolved;\n}\n\n/**\n * Strips a URL prefix from a pathname.\n *\n * Accepts either a resolved prefix or a prefix pattern — segments may be\n * literal strings (must match the pathname exactly) or `:param` placeholders\n * (match any segment value). Mixed prefixes are supported.\n *\n * Returns `''` when the pathname matches the prefix exactly with no remainder\n * (so concatenating `prefix + result` round-trips the input), `pathname`\n * unchanged when literal segments don't match or the path is shorter than the\n * prefix, or the bare remainder otherwise. Callers that need the homepage to\n * be `'/'` should coerce: `stripPathPrefix(...) || '/'`.\n *\n * @example\n * stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'\n * stripPathPrefix({ pathname: '/global/en-GB/checkout', prefix: '/global/en-GB' }) // → '/checkout'\n * stripPathPrefix({ pathname: '/shop/en-GB/x', prefix: '/shop/:localeId' }) // → '/x'\n * stripPathPrefix({ pathname: '/global/en-GB', prefix: '/:siteId/:localeId' }) // → ''\n * stripPathPrefix({ pathname: '/checkout', prefix: '/:siteId/:localeId' }) // → '/checkout'\n * stripPathPrefix({ pathname: '/other/x', prefix: '/global/en-GB' }) // → '/other/x'\n * stripPathPrefix({ pathname: '/x', prefix: '' }) // → '/x'\n */\nexport function stripPathPrefix({ pathname, prefix }: { pathname: string; prefix: string }): string {\n if (!prefix || prefix === '/') return pathname;\n\n const prefixSegments = prefix.split('/').filter(Boolean);\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length < prefixSegments.length) return pathname;\n\n // Literal segments must match exactly; ':param' segments match anything.\n for (let i = 0; i < prefixSegments.length; i++) {\n const segment = prefixSegments[i];\n if (!segment.startsWith(':') && segment !== pathSegments[i]) {\n return pathname;\n }\n }\n\n const remaining = pathSegments.slice(prefixSegments.length);\n return remaining.length === 0 ? '' : `/${remaining.join('/')}`;\n}\n\n/**\n * Builds a fully-qualified URL with site context prefix and search params.\n *\n * Only keys defined in urlConfig.search are set by site context. Any other query params\n * already present on the `to` URL (including duplicate keys) are preserved as-is.\n * e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'\n * → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'\n *\n * @example\n * buildUrl({ to: '/product/123', urlConfig: { prefix: '/:siteId', search: '?lng=:localeId' }, params: { siteId: 'global', localeId: 'en-GB' } })\n * // → '/global/product/123?lng=en-GB'\n */\nexport function buildUrl({\n to,\n urlConfig,\n params,\n}: {\n to: string;\n urlConfig?: Url;\n params: Record<string, string>;\n}): string {\n if (!urlConfig) return to;\n if (!to || to === '#' || to.startsWith('http') || to.startsWith('//')) return to;\n\n const { pathname, search: existingSearch, hash } = decomposeUrl(to);\n\n const pathPrefix =\n urlConfig.prefix && urlConfig.prefix !== '/' ? resolvePrefix({ prefix: urlConfig.prefix, params }) : '';\n const path = pathPrefix ? `${pathPrefix}${stripPathPrefix({ pathname, prefix: pathPrefix })}` : pathname;\n\n const searchParams = new URLSearchParams(existingSearch);\n if (urlConfig.search) {\n const searchConfig = parseSearchConfig(urlConfig.search);\n for (const [queryKey, value] of Object.entries(searchConfig)) {\n if (value.startsWith(':')) {\n const paramValue = params[value.slice(1)];\n if (paramValue) {\n searchParams.set(queryKey, paramValue);\n }\n } else {\n searchParams.set(queryKey, value);\n }\n }\n }\n\n const search = searchParams.toString();\n return `${path}${search ? `?${search}` : ''}${hash}`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\n\n/**\n * Extract a string value from the URL path segment at the given index.\n */\nexport function lookupFromPath(pathname: string, pathIndex: number): string | null {\n const pathSegments = pathname.split('/').filter(Boolean);\n\n if (pathSegments.length <= pathIndex) return null;\n\n return pathSegments[pathIndex];\n}\n\n/**\n * Detect a string value from cookie using the given cookie parser.\n *\n * Returns a promise that resolves to the cookie value.\n */\nexport async function readCookieFromRequest(request: Request, cookie: Cookie): Promise<string | null> {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n const cookieValue = await cookie.parse(cookies);\n return cookieValue;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, SiteSettings, DetectionMethod } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Detect site reference from cookie.\n */\nexport async function readSiteFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get site object using the site id or alias\n * 1. Check siteIdentifier against each site's alias; if matched, return that site.\n * 2. Else check against each site's id; if matched, return that site.\n * 3. If no match, return null.\n */\nfunction getSiteFromIdOrAlias(siteIdentifier: string | null, sites: Site[]): Site | null {\n if (!siteIdentifier) return null;\n return sites.find((site) => site.alias === siteIdentifier || site.id === siteIdentifier) ?? null;\n}\n\n/**\n * Resolve site using the configured detection order.\n * Returns the first valid site from the first source that yields a valid value.\n */\nexport async function resolveSite(request: Request, settings: SiteSettings): Promise<Site> {\n const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;\n\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),\n cookie: async () => readSiteFromCookie(request, siteCookie),\n };\n\n for (const method of siteDetectionConfig.order) {\n const siteIdOrAlias = await resolvers[method]?.();\n const resolvedSite = getSiteFromIdOrAlias(siteIdOrAlias, sites);\n if (resolvedSite) return resolvedSite;\n }\n\n // If no site id was found, use the default site id\n const site = getSiteFromIdOrAlias(defaultSiteId, sites);\n\n // If default site id is invalid, throw an error\n if (!site) {\n throw new Error(`Default site ${defaultSiteId} not found.`);\n }\n\n return site;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { DetectionConfig } from './types';\n\nexport const DEFAULT_CURRENCY_COOKIE_NAME = 'currency';\n\n/**\n * Default site detection configuration\n */\nexport const DEFAULT_SITE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 0,\n lookupQuerystring: 'site',\n lookupCookie: 'site_id',\n lookupHeader: 'X-Site-Id',\n caches: ['cookie'],\n};\n\n/**\n * Default locale detection configuration\n */\nexport const DEFAULT_LOCALE_DETECTION: Required<DetectionConfig> = {\n order: ['path', 'querystring', 'cookie', 'header'],\n lookupFromPathIndex: 1,\n lookupQuerystring: 'lng',\n lookupCookie: 'lng',\n lookupHeader: 'Accept-Language',\n caches: ['cookie'],\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createCookie, type Cookie, type CookieOptions } from 'react-router';\n\n/**\n * Cookie options for site context cookies (site and locale)\n */\nexport const COOKIE_OPTIONS = {\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env.NODE_ENV === 'production',\n httpOnly: true,\n};\n\n/**\n * Creates a cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with site context options\n */\nexport function createSiteContextCookie(name: string, options?: CookieOptions): Cookie {\n return createCookie(name, { ...COOKIE_OPTIONS, ...options });\n}\n\n/**\n * Creates a currency cookie instance with the given name.\n *\n * @param name - Cookie name\n * @returns Cookie instance configured with site context cookie options\n */\nexport function createCurrencyCookie(name: string, options?: CookieOptions): Cookie {\n return createCookie(name, { ...COOKIE_OPTIONS, ...options });\n}\n\n/**\n * WeakMap to pass resolved locale from site context middleware to i18next's findLocale.\n * WeakMap allows garbage collection when requests are done.\n * This is necessary because findLocale() only receives the Request object, not the router context.\n */\nexport const requestToLocaleMap = new WeakMap<Request, string>();\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { DetectionMethod, Locale, SiteSettings, Site } from './types';\nimport { readCookieFromRequest, lookupFromPath } from './utils';\n\n/**\n * Read locale from cookie.\n */\nexport async function readLocaleFromCookie(request: Request, cookie: Cookie): Promise<string | null> {\n return readCookieFromRequest(request, cookie);\n}\n\n/**\n * Get locale object using the locale id or alias.\n * 1. Check localeIdOrAlias against each locale's alias; if matched, return that locale.\n * 2. Else check against each locale's id; if matched, return that locale.\n * 3. If no match, return null (caller should use defaultLocale).\n *\n * @param localeIdentifier - The locale id or alias to get the locale from. Null is allowed because this may come from\n * extrenal sources such as cookies, headers, or query parameters.\n * @param locales - The list of locales to search through.\n * @returns The locale object if found, otherwise null.\n */\nfunction getLocaleFromIdOrAlias(localeIdentifier: string | undefined | null, locales: Locale[]): Locale | null {\n if (!localeIdentifier) return null;\n return locales.find((locale) => locale.alias === localeIdentifier || locale.id === localeIdentifier) ?? null;\n}\n\n/**\n * Resolve locale using the configured detection order.\n * Returns the first valid locale from the first source that yields a valid value.\n */\nexport async function resolveLocale(request: Request, settings: SiteSettings, site: Site): Promise<Locale> {\n const { defaultLocale, localeDetectionConfig, localeCookie } = settings;\n const { supportedLocales } = site;\n\n let locale: Locale | null = null;\n const requestUrl = new URL(request.url);\n\n // When a base path is configured (e.g., '/shop'), we need to skip its path segments.\n // React Router handles the base path internally for hooks like useParams or useLocation,\n // but it does not strip it from request.url. The offset is calculated dynamically from\n // the number of segments in the base path as future-proof in case we support multi-segment\n // base paths in the future.\n const basePathOffset = process.env.MRT_ENV_BASE_PATH\n ? process.env.MRT_ENV_BASE_PATH.split('/').filter(Boolean).length\n : 0;\n\n const resolvers: Record<DetectionMethod, () => Promise<string | null>> = {\n path: () =>\n Promise.resolve(\n lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)\n ),\n querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),\n header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),\n cookie: async () => readLocaleFromCookie(request, localeCookie),\n };\n\n for (const method of localeDetectionConfig.order) {\n const localeIdOrAlias = await resolvers[method]?.();\n const resolvedLocale = getLocaleFromIdOrAlias(localeIdOrAlias, supportedLocales);\n if (resolvedLocale) return resolvedLocale;\n }\n\n // If no locale was found, use the default locale\n if (!locale) {\n locale = getLocaleFromIdOrAlias(defaultLocale, supportedLocales);\n }\n\n // If default locale is invalid, throw an error\n if (!locale) {\n throw new Error(\n `Default locale ${defaultLocale} not found in the list of supported locales for site ${site.id}.`\n );\n }\n\n return locale;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cookie } from 'react-router';\nimport type { Site, Locale } from './types';\nimport { readCookieFromRequest } from './utils';\n\n/**\n * Resolve the currency for the current request.\n *\n * Priority:\n * 1. Cookie (user-selected currency, if valid for this site)\n * 2. Locale's preferred currency (if valid for this site)\n * 3. Site's default currency\n *\n * @param request - Incoming request\n * @param currencyCookie - Cookie instance for reading the currency cookie\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @returns The resolved currency code\n */\nexport async function resolveCurrency(\n request: Request,\n currencyCookie: Cookie,\n site: Site,\n locale: Locale\n): Promise<string> {\n const { supportedCurrencies, defaultCurrency } = site;\n\n if (!supportedCurrencies || supportedCurrencies.length === 0) {\n throw new Error(`Site \"${site.id}\" must have supportedCurrencies configured.`);\n }\n\n // 1. Try cookie\n const cookieValue = await readCookieFromRequest(request, currencyCookie);\n if (typeof cookieValue === 'string' && supportedCurrencies.includes(cookieValue)) {\n return cookieValue;\n }\n\n // 2. Try locale's preferred currency\n if (locale.preferredCurrency && supportedCurrencies.includes(locale.preferredCurrency)) {\n return locale.preferredCurrency;\n }\n\n // 3. Fall back to site default\n return defaultCurrency;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createContext, type MiddlewareFunction, type RouterContextProvider } from 'react-router';\nimport { resolveSite } from './site-detection';\nimport type { SiteConfig, SiteContext, SiteSettings, Site, Locale } from './types';\nimport { DEFAULT_SITE_DETECTION, DEFAULT_LOCALE_DETECTION, DEFAULT_CURRENCY_COOKIE_NAME } from './configs';\nimport { createSiteContextCookie, createCurrencyCookie, requestToLocaleMap } from './cookies';\nimport { resolveLocale } from './locale-detection';\nimport { resolveCurrency } from './currency-detection';\n\nexport const siteContext = createContext<SiteContext | null>(null);\n\n/**\n * Resolved site context result from {@link resolveSiteContext}.\n */\nexport type ResolvedSiteContext = {\n site: Site;\n locale: Locale;\n currency: string;\n};\n\n/**\n * Resolve site, locale, and currency from a request in one call.\n *\n * This is the recommended public entry point for site-context resolution.\n * It encapsulates the required resolution order (site → locale → currency)\n * so consumers don't need to manage the dependency chain manually.\n *\n * The individual resolvers (`resolveSite`, `resolveLocale`, `resolveCurrency`)\n * are available as advanced utilities for cases that need fine-grained control.\n *\n * @param request - Incoming HTTP request\n * @param settings - Fully resolved site settings (with detection config and cookies)\n * @returns Resolved site, locale, and currency\n *\n * @example\n * ```typescript\n * const { site, locale, currency } = await resolveSiteContext(request, settings);\n * ```\n */\nexport async function resolveSiteContext(request: Request, settings: SiteSettings): Promise<ResolvedSiteContext> {\n const site = await resolveSite(request, settings);\n const locale = await resolveLocale(request, settings, site);\n const currency = await resolveCurrency(request, settings.currencyCookie, site, locale);\n return { site, locale, currency };\n}\n\ntype MiddlewareArgs = { request: Request; context: Readonly<RouterContextProvider> };\n\n/**\n * Helper function to get site context cookies from router context.\n * Useful in server actions and loaders that need to read/set cookies.\n *\n * @param context - Router context provider\n * @returns Object with siteCookie and localeCookie instances, or null if context not set\n *\n * @example\n * ```typescript\n * export const action: ActionFunction = async ({ request, context }) => {\n * const cookies = getSiteContextCookies(context);\n * if (cookies) {\n * const cookieHeader = await cookies.localeCookie.serialize(locale);\n * // ... use cookieHeader\n * }\n * };\n * ```\n */\nexport function getSiteContextCookies(context: Readonly<RouterContextProvider>) {\n const siteCtx = context.get(siteContext);\n if (!siteCtx) return null;\n return {\n siteCookie: siteCtx.siteCookie,\n localeCookie: siteCtx.localeCookie,\n currencyCookie: siteCtx.currencyCookie,\n };\n}\n\n/**\n * Helper function to determine if cookies should be set based on:\n * 1. Whether caching is enabled for each cookie type\n * 2. Whether the resolved value differs from the existing cookie\n * 3. Whether cookies were already set by actions/loaders in the response\n *\n * @param request - Incoming request\n * @param response - Response from next()\n * @param settings - Site context settings with cookie instances and detection config\n * @param site - Resolved site for this request\n * @param locale - Resolved locale for this request\n * @param currency - Resolved currency for this request\n * @returns Object with shouldSetSiteCookie, shouldSetLocaleCookie, and shouldSetCurrencyCookie booleans\n */\nasync function shouldSetCookies(\n request: Request,\n response: Response,\n settings: SiteSettings,\n site: Site,\n locale: Locale,\n currency: string\n): Promise<{ shouldSetSiteCookie: boolean; shouldSetLocaleCookie: boolean; shouldSetCurrencyCookie: boolean }> {\n const cacheSite = settings.siteDetectionConfig.caches?.includes('cookie');\n const cacheLocale = settings.localeDetectionConfig.caches?.includes('cookie');\n\n // Check if cookies were already set by actions/loaders in the response.\n // If they were, we don't want to override them.\n const responseSetCookies = response.headers.getSetCookie?.() || [];\n const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.siteCookie.name}=`)\n );\n const isSettingLocaleCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.localeCookie.name}=`)\n );\n const isSettingCurrencyCookieInResponse = responseSetCookies.some((cookie) =>\n cookie.startsWith(`${settings.currencyCookie.name}=`)\n );\n\n const requestCookieHeader = request.headers.get('Cookie');\n const [existingSiteCookie, existingLocaleCookie, existingCurrencyCookie] = await Promise.all([\n settings.siteCookie.parse(requestCookieHeader),\n settings.localeCookie.parse(requestCookieHeader),\n settings.currencyCookie.parse(requestCookieHeader),\n ]);\n\n // Set cookie if: doesn't exist yet OR resolved value differs from existing.\n // Skip if an action/loader already set it in the response.\n return {\n shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,\n shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id,\n shouldSetCurrencyCookie: !isSettingCurrencyCookieInResponse && existingCurrencyCookie !== currency,\n };\n}\n\n/**\n * Creates a site context middleware that resolves the current site from\n * the request (path, cookie, header, query, or default) and stores the\n * result in the router context.\n *\n * Does not import or read from app config context; the consumer supplies config.\n */\nexport function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response> {\n // Merge config with defaults so every detection option has a value\n const siteDetectionConfig: SiteSettings['siteDetectionConfig'] = {\n ...DEFAULT_SITE_DETECTION,\n ...config.siteDetectionConfig,\n };\n const localeDetectionConfig: SiteSettings['localeDetectionConfig'] = {\n ...DEFAULT_LOCALE_DETECTION,\n ...config.localeDetectionConfig,\n };\n\n // Create cookies based on configured names and optional cookie options\n const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie, config.cookieOptions);\n const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie, config.cookieOptions);\n const currencyCookie = createCurrencyCookie(\n config.currencyCookieName ?? DEFAULT_CURRENCY_COOKIE_NAME,\n config.cookieOptions\n );\n\n const settings: SiteSettings = {\n ...config,\n siteDetectionConfig,\n localeDetectionConfig,\n siteCookie,\n localeCookie,\n currencyCookie,\n };\n\n const siteContextMiddleware: MiddlewareFunction<Response> = async (\n { request, context }: MiddlewareArgs,\n next: () => Promise<Response>\n ): Promise<Response> => {\n const { site, locale, currency } = await resolveSiteContext(request, settings);\n\n // Store full Site, Locale, Currency, and Cookie objects in context\n context.set(siteContext, {\n site,\n locale,\n currency,\n siteCookie: settings.siteCookie,\n localeCookie: settings.localeCookie,\n currencyCookie: settings.currencyCookie,\n });\n\n // Store locale in a WeakMap so i18next's findLocale can access it\n // This is necessary because findLocale only receives Request and cannot access the router context\n requestToLocaleMap.set(request, locale.id);\n\n const response = await next();\n\n // Determine if cookies should be set.\n // Actions that change currency (or site/locale) set their cookies directly on the response;\n // shouldSetCookies detects those Set-Cookie headers and skips to avoid overriding.\n const { shouldSetSiteCookie, shouldSetLocaleCookie, shouldSetCurrencyCookie } = await shouldSetCookies(\n request,\n response,\n settings,\n site,\n locale,\n currency\n );\n\n // Early return if no cookies need to be set\n if (!shouldSetSiteCookie && !shouldSetLocaleCookie && !shouldSetCurrencyCookie) {\n return response;\n }\n\n const [siteSetCookie, localeSetCookie, currencySetCookie] = await Promise.all([\n shouldSetSiteCookie ? settings.siteCookie.serialize(site.id, { path: '/' }) : Promise.resolve(null),\n shouldSetLocaleCookie ? settings.localeCookie.serialize(locale.id, { path: '/' }) : Promise.resolve(null),\n shouldSetCurrencyCookie ? settings.currencyCookie.serialize(currency) : Promise.resolve(null),\n ]);\n\n if (siteSetCookie) response.headers.append('Set-Cookie', siteSetCookie);\n if (localeSetCookie) response.headers.append('Set-Cookie', localeSetCookie);\n if (currencySetCookie) response.headers.append('Set-Cookie', currencySetCookie);\n\n return response;\n };\n\n return siteContextMiddleware;\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Baseline configuration shapes for templates that have a notion of sites,
|
|
19
|
+
* locales, and URL prefixes. These are opt-in helpers, not enforced contracts —
|
|
20
|
+
* `BaseConfig<App>` is generic over the template's `App` shape, so a template
|
|
21
|
+
* with a different shopper model (e.g. B2B, OMS-only) can define its own types
|
|
22
|
+
* and ignore the ones below.
|
|
23
|
+
*
|
|
24
|
+
* For richer runtime shapes used by the site context middleware (with
|
|
25
|
+
* cookie-based detection, site/locale aliases, etc.) see
|
|
26
|
+
* `@salesforce/storefront-next-runtime/site-context`.
|
|
27
|
+
*/
|
|
17
28
|
type Locale = {
|
|
18
29
|
id: string;
|
|
19
30
|
preferredCurrency: string;
|
|
@@ -30,11 +41,16 @@ type Site = {
|
|
|
30
41
|
supportedLocales: Array<Locale>;
|
|
31
42
|
};
|
|
32
43
|
type Url = {
|
|
33
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* URL path prefix using React Router param syntax — interpolated by the
|
|
46
|
+
* template's URL builder. Templates with site/locale routing typically use
|
|
47
|
+
* something like `'/:siteId/:localeId'`; templates without routing params
|
|
48
|
+
* can omit this field.
|
|
49
|
+
*/
|
|
34
50
|
prefix?: string;
|
|
35
51
|
/**
|
|
36
|
-
* Query parameters to append to URLs, using
|
|
37
|
-
*
|
|
52
|
+
* Query parameters to append to URLs, using `:param` syntax — interpolated
|
|
53
|
+
* by the template's URL builder. Pair shape with `prefix` above.
|
|
38
54
|
*/
|
|
39
55
|
search?: string;
|
|
40
56
|
excludeRoutes?: string[];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/config/types.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/config/types.ts"],"sourcesContent":[],"mappings":";;AA4BA;AAKA;AAYA;;;;;;;;;;;;;;;;;;;;;;;KAjBY,MAAA;;;;KAKA,IAAA;;;;;;;;;oBASU,MAAM;;KAGhB,GAAA"}
|