@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.
Files changed (46) hide show
  1. package/dist/DesignFrame.js +6 -2
  2. package/dist/DesignFrame.js.map +1 -1
  3. package/dist/DesignRegion.js +2 -1
  4. package/dist/DesignRegion.js.map +1 -1
  5. package/dist/apply-url-config.js +4 -4
  6. package/dist/apply-url-config.js.map +1 -1
  7. package/dist/component.types.d.ts +6 -0
  8. package/dist/component.types.d.ts.map +1 -1
  9. package/dist/config-load.d.ts +27 -0
  10. package/dist/config-load.d.ts.map +1 -0
  11. package/dist/config-load.js +3 -0
  12. package/dist/config.d.ts +248 -1
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +432 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/design-data.d.ts +40 -27
  17. package/dist/design-data.d.ts.map +1 -1
  18. package/dist/design-data.js +50 -26
  19. package/dist/design-data.js.map +1 -1
  20. package/dist/design-react-core.d.ts +2 -2
  21. package/dist/design-react-core.js +3 -1
  22. package/dist/design-react-core.js.map +1 -1
  23. package/dist/events.d.ts +9 -4
  24. package/dist/events.d.ts.map +1 -1
  25. package/dist/events.js +6 -6
  26. package/dist/events.js.map +1 -1
  27. package/dist/load-config.js +42 -0
  28. package/dist/load-config.js.map +1 -0
  29. package/dist/routing.d.ts +1 -1
  30. package/dist/routing.d.ts.map +1 -1
  31. package/dist/routing.js +5 -38
  32. package/dist/routing.js.map +1 -1
  33. package/dist/scapi.d.ts +8 -0
  34. package/dist/scapi.d.ts.map +1 -1
  35. package/dist/scapi.js +1 -1
  36. package/dist/scapi.js.map +1 -1
  37. package/dist/schema.d.ts +78 -0
  38. package/dist/schema.d.ts.map +1 -0
  39. package/dist/{multi-site.d.ts → site-context.d.ts} +85 -60
  40. package/dist/site-context.d.ts.map +1 -0
  41. package/dist/{multi-site.js → site-context.js} +67 -41
  42. package/dist/site-context.js.map +1 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +10 -4
  45. package/dist/multi-site.d.ts.map +0 -1
  46. package/dist/multi-site.js.map +0 -1
@@ -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 react_jsx_runtime0 from "react/jsx-runtime";
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/multi-site/site-context.d.ts
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$1;
22
- }>): react_jsx_runtime0.JSX.Element;
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$1 | undefined;
64
+ declare function useSite(): Site | undefined;
28
65
  //#endregion
29
- //#region src/multi-site/apply-url-config.d.ts
30
-
66
+ //#region src/site-context/apply-url-config.d.ts
31
67
  /**
32
- * Applies multi-site URL configuration to a set of route entries.
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/multi-site/build-url.d.ts
52
-
87
+ //#region src/site-context/build-url.d.ts
53
88
  /**
54
- * Builds a fully-qualified URL with multi-site prefix and search params.
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
- * Only keys defined in urlConfig.search are set by multi-site. Any other query params
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/multi-site/types.d.ts
76
- type Locale = Locale$1 & {
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 multi-site cookies from router context.
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 = getMultiSiteCookies(context);
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 getMultiSiteCookies(context: Readonly<RouterContextProvider>): {
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 multi-site middleware that resolves the current site from
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 createMultiSiteMiddleware(config: MultiSiteConfig): MiddlewareFunction<Response>;
168
+ declare function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunction<Response>;
144
169
  //#endregion
145
- //#region src/multi-site/cookies.d.ts
170
+ //#region src/site-context/cookies.d.ts
146
171
  /**
147
- * WeakMap to pass resolved locale from multi-site middleware to i18next's findLocale.
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 MultiSiteConfig, type MultiSiteContext, type Site, SiteProvider, applyUrlConfig, buildUrl, createMultiSiteMiddleware, getMultiSiteCookies, multiSiteContext, requestToLocaleMap, useSite };
154
- //# sourceMappingURL=multi-site.d.ts.map
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/multi-site/site-context.tsx
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/multi-site/build-url.ts
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 multi-site prefix and search params.
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 multi-site. Any other query params
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/multi-site/utils.ts
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/multi-site/site-detection.ts
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/multi-site/configs.ts
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/multi-site/cookies.ts
238
+ //#region src/site-context/cookies.ts
216
239
  /**
217
- * Cookie options for multi-site cookies
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 multi-site options
252
+ * @returns Cookie instance configured with site context options
230
253
  */
231
- function createMultiSiteCookie(name) {
254
+ function createSiteContextCookie(name) {
232
255
  return createCookie(name, COOKIE_OPTIONS);
233
256
  }
234
257
  /**
235
- * WeakMap to pass resolved locale from multi-site middleware to i18next's findLocale.
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/multi-site/locale-detection.ts
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/multi-site/middleware.ts
290
- const multiSiteContext = createContext$1(null);
313
+ //#region src/site-context/middleware.ts
314
+ const siteContext = createContext$1(null);
291
315
  /**
292
- * Helper function to get multi-site cookies from router context.
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 = getMultiSiteCookies(context);
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 getMultiSiteCookies(context) {
310
- const multiSite = context.get(multiSiteContext);
311
- if (!multiSite) return null;
333
+ function getSiteContextCookies(context) {
334
+ const siteCtx = context.get(siteContext);
335
+ if (!siteCtx) return null;
312
336
  return {
313
- siteCookie: multiSite.siteCookie,
314
- localeCookie: multiSite.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 cookies already exist in the incoming request
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 - Multi-site settings with cookie instances and detection config
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 && !existingSiteCookie && !isSettingSiteCookieInResponse,
342
- shouldSetLocaleCookie: cacheLocale && !existingLocaleCookie && !isSettingLocaleCookieInResponse
367
+ shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,
368
+ shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id
343
369
  };
344
370
  }
345
371
  /**
346
- * Creates a multi-site middleware that resolves the current site from
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 createMultiSiteMiddleware(config) {
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 = createMultiSiteCookie(siteDetectionConfig.lookupCookie);
362
- const localeCookie = createMultiSiteCookie(localeDetectionConfig.lookupCookie);
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 multiSiteMiddleware = async ({ request, context }, next) => {
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(multiSiteContext, {
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 multiSiteMiddleware;
414
+ return siteContextMiddleware;
389
415
  }
390
416
 
391
417
  //#endregion
392
- export { SiteProvider, applyUrlConfig, buildUrl, createMultiSiteMiddleware, getMultiSiteCookies, multiSiteContext, requestToLocaleMap, useSite };
393
- //# sourceMappingURL=multi-site.js.map
418
+ export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
419
+ //# sourceMappingURL=site-context.js.map