@salesforce/storefront-next-runtime 0.3.1-alpha.1 → 0.4.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.
Files changed (66) hide show
  1. package/README.md +82 -0
  2. package/dist/DesignComponent.js +37 -12
  3. package/dist/DesignComponent.js.map +1 -1
  4. package/dist/DesignContext.js +47 -2
  5. package/dist/DesignContext.js.map +1 -1
  6. package/dist/DesignFrame.js +1 -1
  7. package/dist/DesignRegion.js +1 -1
  8. package/dist/config.d.ts +4 -4
  9. package/dist/custom-global-preferences.d.ts +20 -0
  10. package/dist/custom-global-preferences.d.ts.map +1 -0
  11. package/dist/custom-global-preferences.js +28 -0
  12. package/dist/custom-global-preferences.js.map +1 -0
  13. package/dist/custom-site-preferences.d.ts +20 -0
  14. package/dist/custom-site-preferences.d.ts.map +1 -0
  15. package/dist/custom-site-preferences.js +28 -0
  16. package/dist/custom-site-preferences.js.map +1 -0
  17. package/dist/data-store-custom-global-preferences.d.ts +2 -0
  18. package/dist/data-store-custom-global-preferences.js +6 -0
  19. package/dist/data-store-custom-site-preferences.d.ts +2 -0
  20. package/dist/data-store-custom-site-preferences.js +6 -0
  21. package/dist/data-store-gcp-preferences.d.ts +2 -0
  22. package/dist/data-store-gcp-preferences.js +6 -0
  23. package/dist/data-store.d.ts +97 -0
  24. package/dist/data-store.d.ts.map +1 -0
  25. package/dist/data-store.js +42 -0
  26. package/dist/data-store.js.map +1 -0
  27. package/dist/design-data.d.ts +82 -88
  28. package/dist/design-data.d.ts.map +1 -1
  29. package/dist/design-data.js +95 -57
  30. package/dist/design-data.js.map +1 -1
  31. package/dist/design-messaging.d.ts +2 -2
  32. package/dist/events.d.ts +34 -6
  33. package/dist/events.d.ts.map +1 -1
  34. package/dist/events.js +6 -6
  35. package/dist/events.js.map +1 -1
  36. package/dist/gcp-preferences.d.ts +52 -0
  37. package/dist/gcp-preferences.d.ts.map +1 -0
  38. package/dist/gcp-preferences.js +61 -0
  39. package/dist/gcp-preferences.js.map +1 -0
  40. package/dist/i18n-client.d.ts +38 -0
  41. package/dist/i18n-client.d.ts.map +1 -0
  42. package/dist/i18n-client.js +72 -0
  43. package/dist/i18n-client.js.map +1 -0
  44. package/dist/i18n.d.ts +63 -0
  45. package/dist/i18n.d.ts.map +1 -0
  46. package/dist/i18n.js +98 -0
  47. package/dist/i18n.js.map +1 -0
  48. package/dist/index.d.ts +60 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/messaging-api.js +3 -1
  51. package/dist/messaging-api.js.map +1 -1
  52. package/dist/scapi.d.ts +247 -2
  53. package/dist/scapi.d.ts.map +1 -1
  54. package/dist/scapi.js +1 -1
  55. package/dist/scapi.js.map +1 -1
  56. package/dist/site-context.d.ts +94 -18
  57. package/dist/site-context.d.ts.map +1 -1
  58. package/dist/site-context.js +2 -417
  59. package/dist/site-context2.js +513 -0
  60. package/dist/site-context2.js.map +1 -0
  61. package/dist/types2.d.ts +210 -0
  62. package/dist/types2.d.ts.map +1 -1
  63. package/dist/utils.js +179 -0
  64. package/dist/utils.js.map +1 -0
  65. package/package.json +63 -4
  66. package/dist/site-context.js.map +0 -1
@@ -1,8 +1,8 @@
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";
4
- import * as react_router0 from "react-router";
5
- import { Cookie, MiddlewareFunction, RouterContextProvider } from "react-router";
3
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
4
+ import * as react_router10 from "react-router";
5
+ import { Cookie, CookieOptions, MiddlewareFunction, RouterContextProvider } from "react-router";
6
6
  import { RouteConfigEntry } from "@react-router/dev/routes";
7
7
 
8
8
  //#region src/site-context/types.d.ts
@@ -18,8 +18,10 @@ type Site = Omit<Site$1, 'supportedLocales'> & {
18
18
  type SiteContext = {
19
19
  site: Site;
20
20
  locale: Locale;
21
+ currency: string;
21
22
  siteCookie: Cookie;
22
23
  localeCookie: Cookie;
24
+ currencyCookie: Cookie;
23
25
  };
24
26
  /**
25
27
  * Configuration passed into the site context middleware
@@ -31,6 +33,18 @@ type SiteConfig = {
31
33
  defaultLocale: string;
32
34
  siteDetectionConfig?: DetectionConfig;
33
35
  localeDetectionConfig?: DetectionConfig;
36
+ currencyCookieName?: string;
37
+ cookieOptions?: CookieOptions;
38
+ };
39
+ /**
40
+ * Resolved settings used by site/locale/currency resolution (all detection options have values).
41
+ */
42
+ type SiteSettings = SiteConfig & {
43
+ siteDetectionConfig: Required<DetectionConfig>;
44
+ localeDetectionConfig: Required<DetectionConfig>;
45
+ siteCookie: Cookie;
46
+ localeCookie: Cookie;
47
+ currencyCookie: Cookie;
34
48
  };
35
49
  /** Detection method identifier (used for both site and locale detection) */
36
50
  type DetectionMethod = 'path' | 'querystring' | 'cookie' | 'header';
@@ -45,23 +59,56 @@ type DetectionConfig = {
45
59
  //#endregion
46
60
  //#region src/site-context/site-context.d.ts
47
61
  /**
48
- * Provides the current site to the component tree.
49
- * Follows the same pattern as CurrencyProvider.
62
+ * The value provided by {@link SiteProvider} and returned by {@link useSite}.
63
+ */
64
+ type SiteContextValue = {
65
+ /**
66
+ * The resolved site configuration object for the current request.
67
+ * Contains the site's supported locales, supported currencies, and default values.
68
+ */
69
+ site: Site;
70
+ /**
71
+ * The full locale object from the site's `supportedLocales` list for the current request.
72
+ * Contains structured locale metadata: `id` (e.g. `"en-GB"`), optional `alias` (e.g. `"en"`),
73
+ * and optional `preferredCurrency`.
74
+ *
75
+ */
76
+ locale: Locale;
77
+ /**
78
+ * The current i18next language string (e.g. `"en-GB"`, `"fr-FR"`).
79
+ * This is the value returned by `i18next.language` and drives which translation
80
+ * namespace is active. Passed as a prop because the SDK has no react-i18next dependency.
81
+ *
82
+ * @see {@link SiteContextValue.locale} for the full locale object from site config.
83
+ */
84
+ language: string;
85
+ /**
86
+ * The active currency code for the current session (e.g. `"USD"`, `"GBP"`).
87
+ * Resolved from the locale's `preferredCurrency`, a currency cookie, or the site's
88
+ * `defaultCurrency`.
89
+ */
90
+ currency: string;
91
+ };
92
+ /**
93
+ * Provides the current site context (site, locale, language, currency) to the component tree.
50
94
  *
51
- * Mounted in the template (e.g., app-wrapper.tsx or root.tsx) with the resolved
52
- * site value from the loader/middleware.
95
+ * Mounted in the template's root.tsx with the resolved values from the
96
+ * loader/middleware. The SDK has no react-i18next dependency, so `language`
97
+ * is passed as a prop from the template.
53
98
  */
54
99
  declare function SiteProvider({
55
- value,
100
+ site,
101
+ locale,
102
+ language,
103
+ currency,
56
104
  children
57
- }: PropsWithChildren<{
58
- value: Site;
59
- }>): react_jsx_runtime0.JSX.Element;
105
+ }: PropsWithChildren<SiteContextValue>): react_jsx_runtime2.JSX.Element;
60
106
  /**
61
- * React hook to get the current site.
62
- * Returns undefined when no SiteProvider is mounted.
107
+ * React hook to get the current site context.
108
+ * Returns `{ site, locale, language, currency }`.
109
+ * @throws If called outside of a SiteProvider
63
110
  */
64
- declare function useSite(): Site | undefined;
111
+ declare function useSite(): SiteContextValue;
65
112
  //#endregion
66
113
  //#region src/site-context/apply-url-config.d.ts
67
114
  /**
@@ -135,7 +182,35 @@ declare function buildUrl({
135
182
  }): string;
136
183
  //#endregion
137
184
  //#region src/site-context/middleware.d.ts
138
- declare const siteContext: react_router0.RouterContext<SiteContext | null>;
185
+ declare const siteContext: react_router10.RouterContext<SiteContext | null>;
186
+ /**
187
+ * Resolved site context result from {@link resolveSiteContext}.
188
+ */
189
+ type ResolvedSiteContext = {
190
+ site: Site;
191
+ locale: Locale;
192
+ currency: string;
193
+ };
194
+ /**
195
+ * Resolve site, locale, and currency from a request in one call.
196
+ *
197
+ * This is the recommended public entry point for site-context resolution.
198
+ * It encapsulates the required resolution order (site → locale → currency)
199
+ * so consumers don't need to manage the dependency chain manually.
200
+ *
201
+ * The individual resolvers (`resolveSite`, `resolveLocale`, `resolveCurrency`)
202
+ * are available as advanced utilities for cases that need fine-grained control.
203
+ *
204
+ * @param request - Incoming HTTP request
205
+ * @param settings - Fully resolved site settings (with detection config and cookies)
206
+ * @returns Resolved site, locale, and currency
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const { site, locale, currency } = await resolveSiteContext(request, settings);
211
+ * ```
212
+ */
213
+ declare function resolveSiteContext(request: Request, settings: SiteSettings): Promise<ResolvedSiteContext>;
139
214
  /**
140
215
  * Helper function to get site context cookies from router context.
141
216
  * Useful in server actions and loaders that need to read/set cookies.
@@ -155,8 +230,9 @@ declare const siteContext: react_router0.RouterContext<SiteContext | null>;
155
230
  * ```
156
231
  */
157
232
  declare function getSiteContextCookies(context: Readonly<RouterContextProvider>): {
158
- siteCookie: react_router0.Cookie;
159
- localeCookie: react_router0.Cookie;
233
+ siteCookie: react_router10.Cookie;
234
+ localeCookie: react_router10.Cookie;
235
+ currencyCookie: react_router10.Cookie;
160
236
  } | null;
161
237
  /**
162
238
  * Creates a site context middleware that resolves the current site from
@@ -175,5 +251,5 @@ declare function createSiteContextMiddleware(config: SiteConfig): MiddlewareFunc
175
251
  */
176
252
  declare const requestToLocaleMap: WeakMap<Request, string>;
177
253
  //#endregion
178
- export { type DetectionConfig, type Locale, type Site, type SiteConfig, type SiteContext, SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
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, sanitizePrefix, siteContext, stripPathPrefix, useSite };
179
255
  //# 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":";;;;;;;;;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"}
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;AGjBb,CAAA;;;;;;AHrB0B,KCfd,gBAAA,GDec;EAOd;;;;EAOQ,IAAA,ECxBV,IDwBU;EAAa;AAMjC;;;;;EAE2B,MAAA,ECxBf,MDwBe;EACX;;;;AAMhB;AAGA;;;;AC/CA;AAyCA;;;EAA6C,QAAA,EAAA,MAAA;CAAU;;;;;;AAWvD;;iBAXgB,YAAA;;;;;;GAA6D,kBAAkB,oBAAiB,kBAAA,CAAA,GAAA,CAAA;;AC0DhH;;;;AAIoB,iBDnDJ,OAAA,CAAA,CCmDI,EDnDO,gBCmDP;;;;;ADvGpB;AAyCA;;;;;;;;;;AAWA;iBC+CgB,cAAA;UACJ;cACI;EAFA,WAAA,EAAA,MAAc;CAClB,CAAA,EAGR,gBAHQ,EAAA;;;;;AFxDZ;AAGA;iBGXgB,aAAA,yBAAsC;;;AFpCtD;AAyCA;;;;;;;;;;AAWA;;iBEUgB,eAAA;;ADqChB;;;;AAIoB,iBCvBJ,cAAA,CDuBI,QAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;ACnEpB;AA0BA;AAkBA;AAmBA;;;;;;AAOkB,iBAPF,QAAA,CAOE;EAAA,EAAA;EAAA,SAAA;EAAA;ACvGlB,CDuGkB,EAAA;;cADF;UACJ;ACvGZ,CAAA,CAAA,EAAa,MAAA;;;AJUG,cIVH,WJUG,EIVQ,cAAA,CAAA,aJUR,CIVQ,WJUR,GAAA,IAAA,CAAA;;;;AASJ,KIdA,mBAAA,GJcU;EACX,IAAA,EIdD,IJcC;EAGe,MAAA,EIhBd,MJgBc;EACE,QAAA,EAAA,MAAA;CAER;;AAMpB;;;;;;;;;;AASA;AAGA;;;;AC/CA;AAyCA;;AAAqC,iBGRf,kBAAA,CHQe,OAAA,EGRa,OHQb,EAAA,QAAA,EGRgC,YHQhC,CAAA,EGR+C,OHQ/C,CGRuD,mBHQvD,CAAA;;;;;;;;AAWrC;;;;AC+CA;;;;;;;iBEvCgB,qBAAA,UAA+B,SAAS;EDxBxC,UAAA,ECwBuC,cAAA,CAAA,MDxBK;EA0B5C,YAAA,uBAAe;EAkBf,cAAA,uBAAc;AAmB9B,CAAA,GAAgB,IAAA;;;;;;;;iBCgCA,2BAAA,SAAoC,aAAa,mBAAmB;;;;;;AHnIpF;AAyCA;AAA+B,cITlB,kBJSkB,EITA,OJSA,CITA,OJSA,EAAA,MAAA,CAAA"}
@@ -1,419 +1,4 @@
1
+ import { a as requestToLocaleMap, c as sanitizePrefix, d as useSite, i as siteContext, l as stripPathPrefix, n as getSiteContextCookies, o as buildUrl, r as resolveSiteContext, s as resolvePrefix, t as createSiteContextMiddleware, u as SiteProvider } from "./site-context2.js";
1
2
  import { t as applyUrlConfig } from "./apply-url-config.js";
2
- import { createContext, useContext } from "react";
3
- import { jsx } from "react/jsx-runtime";
4
- import { createContext as createContext$1, createCookie } from "react-router";
5
3
 
6
- //#region src/site-context/site-context.tsx
7
- const SiteContext = createContext(void 0);
8
- /**
9
- * Provides the current site to the component tree.
10
- * Follows the same pattern as CurrencyProvider.
11
- *
12
- * Mounted in the template (e.g., app-wrapper.tsx or root.tsx) with the resolved
13
- * site value from the loader/middleware.
14
- */
15
- function SiteProvider({ value, children }) {
16
- return /* @__PURE__ */ jsx(SiteContext.Provider, {
17
- value,
18
- children
19
- });
20
- }
21
- /**
22
- * React hook to get the current site.
23
- * Returns undefined when no SiteProvider is mounted.
24
- */
25
- function useSite() {
26
- return useContext(SiteContext);
27
- }
28
-
29
- //#endregion
30
- //#region src/site-context/build-url.ts
31
- /**
32
- * Parses search config string into key-value pairs, preserving ':param' placeholders.
33
- * '?lng=:localeId&site=:siteId' → { lng: ':localeId', site: ':siteId' }
34
- */
35
- function parseSearchConfig(search) {
36
- const searchParams = new URLSearchParams(search);
37
- const result = {};
38
- for (const [key, value] of searchParams) result[key] = value;
39
- return result;
40
- }
41
- /**
42
- * Extracts parameter names from a prefix string.
43
- * '/:siteId/:localeId' → ['siteId', 'localeId']
44
- */
45
- function extractPrefixParams(prefix) {
46
- const matches = prefix.match(/:(\w+)/g);
47
- return matches ? matches.map((m) => m.slice(1)) : [];
48
- }
49
- /**
50
- * Splits a URL string into its component parts.
51
- * '/product/123?color=red#details' → { pathname: '/product/123', search: 'color=red', hash: '#details' }
52
- */
53
- function decomposeUrl(url) {
54
- const hashIdx = url.indexOf("#");
55
- const hash = hashIdx >= 0 ? url.slice(hashIdx) : "";
56
- const withoutHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
57
- const searchIdx = withoutHash.indexOf("?");
58
- const search = searchIdx >= 0 ? withoutHash.slice(searchIdx + 1) : "";
59
- return {
60
- pathname: searchIdx >= 0 ? withoutHash.slice(0, searchIdx) : withoutHash,
61
- search,
62
- hash
63
- };
64
- }
65
- /**
66
- * Resolves a prefix template by replacing parameter placeholders with values.
67
- * ('/:siteId/:localeId', { siteId: 'global', localeId: 'en-GB' }) → '/global/en-GB'
68
- */
69
- function resolvePrefix(prefix, params) {
70
- let resolved = prefix;
71
- for (const paramName of extractPrefixParams(prefix)) {
72
- const value = params[paramName];
73
- if (value) resolved = resolved.replace(`:${paramName}`, value);
74
- }
75
- return resolved;
76
- }
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
- /**
100
- * Sanitize a resolved prefix from a pathname if present.
101
- * sanitizePrefix('/global/en-GB/product/123', '/global/en-GB') → '/product/123'
102
- * sanitizePrefix('/product/123', '/global/en-GB') → '/product/123' (no-op)
103
- */
104
- function sanitizePrefix(pathname, pathPrefix) {
105
- if (!pathPrefix) return pathname;
106
- if (pathname === pathPrefix) return "";
107
- if (pathname.startsWith(`${pathPrefix}/`)) return pathname.slice(pathPrefix.length);
108
- return pathname;
109
- }
110
- /**
111
- * Builds a fully-qualified URL with site context prefix and search params.
112
- *
113
- * Only keys defined in urlConfig.search are set by site context. Any other query params
114
- * already present on the `to` URL (including duplicate keys) are preserved as-is.
115
- * e.g. to='/api/search?refine=color:blue&refine=size:M', search='?lng=:localeId'
116
- * → '/api/search?refine=color:blue&refine=size:M&lng=en-GB'
117
- *
118
- * @example
119
- * buildUrl({ to: '/product/123', urlConfig: { prefix: '/:siteId', search: '?lng=:localeId' }, params: { siteId: 'global', localeId: 'en-GB' } })
120
- * // → '/global/product/123?lng=en-GB'
121
- */
122
- function buildUrl({ to, urlConfig, params }) {
123
- if (!urlConfig) return to;
124
- if (!to || to === "#" || to.startsWith("http") || to.startsWith("//")) return to;
125
- const { pathname, search: existingSearch, hash } = decomposeUrl(to);
126
- const pathPrefix = urlConfig.prefix && urlConfig.prefix !== "/" ? resolvePrefix(urlConfig.prefix, params) : "";
127
- const path = pathPrefix ? `${pathPrefix}${sanitizePrefix(pathname, pathPrefix)}` : pathname;
128
- const searchParams = new URLSearchParams(existingSearch);
129
- if (urlConfig.search) {
130
- const searchConfig = parseSearchConfig(urlConfig.search);
131
- for (const [queryKey, value] of Object.entries(searchConfig)) if (value.startsWith(":")) {
132
- const paramValue = params[value.slice(1)];
133
- if (paramValue) searchParams.set(queryKey, paramValue);
134
- } else searchParams.set(queryKey, value);
135
- }
136
- const search = searchParams.toString();
137
- return `${path}${search ? `?${search}` : ""}${hash}`;
138
- }
139
-
140
- //#endregion
141
- //#region src/site-context/utils.ts
142
- /**
143
- * Extract a string value from the URL path segment at the given index.
144
- */
145
- function lookupFromPath(pathname, pathIndex) {
146
- const pathSegments = pathname.split("/").filter(Boolean);
147
- if (pathSegments.length <= pathIndex) return null;
148
- return pathSegments[pathIndex];
149
- }
150
- /**
151
- * Detect a string value from cookie using the given cookie parser.
152
- *
153
- * Returns a promise that resolves to the cookie value.
154
- */
155
- async function readCookieFromRequest(request, cookie) {
156
- const cookies = request.headers.get("Cookie");
157
- if (!cookies) return null;
158
- return await cookie.parse(cookies);
159
- }
160
-
161
- //#endregion
162
- //#region src/site-context/site-detection.ts
163
- /**
164
- * Detect site reference from cookie.
165
- */
166
- async function readSiteFromCookie(request, cookie) {
167
- return readCookieFromRequest(request, cookie);
168
- }
169
- /**
170
- * Get site object using the site id or alias
171
- * 1. Check siteIdentifier against each site's alias; if matched, return that site.
172
- * 2. Else check against each site's id; if matched, return that site.
173
- * 3. If no match, return null.
174
- */
175
- function getSiteFromIdOrAlias(siteIdentifier, sites) {
176
- if (!siteIdentifier) return null;
177
- return sites.find((site) => site.alias === siteIdentifier || site.id === siteIdentifier) ?? null;
178
- }
179
- /**
180
- * Resolve site using the configured detection order.
181
- * Returns the first valid site from the first source that yields a valid value.
182
- */
183
- async function resolveSite(request, settings) {
184
- const { sites, defaultSiteId, siteDetectionConfig, siteCookie } = settings;
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;
187
- const resolvers = {
188
- path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, siteDetectionConfig.lookupFromPathIndex + basePathOffset)),
189
- querystring: () => Promise.resolve(requestUrl.searchParams.get(siteDetectionConfig.lookupQuerystring)),
190
- header: () => Promise.resolve(request.headers.get(siteDetectionConfig.lookupHeader)),
191
- cookie: async () => readSiteFromCookie(request, siteCookie)
192
- };
193
- for (const method of siteDetectionConfig.order) {
194
- const resolvedSite = getSiteFromIdOrAlias(await resolvers[method]?.(), sites);
195
- if (resolvedSite) return resolvedSite;
196
- }
197
- const site = getSiteFromIdOrAlias(defaultSiteId, sites);
198
- if (!site) throw new Error(`Default site ${defaultSiteId} not found.`);
199
- return site;
200
- }
201
-
202
- //#endregion
203
- //#region src/site-context/configs.ts
204
- /**
205
- * Default site detection configuration
206
- */
207
- const DEFAULT_SITE_DETECTION = {
208
- order: [
209
- "path",
210
- "querystring",
211
- "cookie",
212
- "header"
213
- ],
214
- lookupFromPathIndex: 0,
215
- lookupQuerystring: "site",
216
- lookupCookie: "site_id",
217
- lookupHeader: "X-Site-Id",
218
- caches: ["cookie"]
219
- };
220
- /**
221
- * Default locale detection configuration
222
- */
223
- const DEFAULT_LOCALE_DETECTION = {
224
- order: [
225
- "path",
226
- "querystring",
227
- "cookie",
228
- "header"
229
- ],
230
- lookupFromPathIndex: 1,
231
- lookupQuerystring: "lng",
232
- lookupCookie: "lng",
233
- lookupHeader: "Accept-Language",
234
- caches: ["cookie"]
235
- };
236
-
237
- //#endregion
238
- //#region src/site-context/cookies.ts
239
- /**
240
- * Cookie options for site context cookies
241
- */
242
- const COOKIE_OPTIONS = {
243
- path: "/",
244
- sameSite: "lax",
245
- secure: process.env.NODE_ENV === "production",
246
- httpOnly: true
247
- };
248
- /**
249
- * Creates a cookie instance with the given name.
250
- *
251
- * @param name - Cookie name
252
- * @returns Cookie instance configured with site context options
253
- */
254
- function createSiteContextCookie(name) {
255
- return createCookie(name, COOKIE_OPTIONS);
256
- }
257
- /**
258
- * WeakMap to pass resolved locale from site context middleware to i18next's findLocale.
259
- * WeakMap allows garbage collection when requests are done.
260
- * This is necessary because findLocale() only receives the Request object, not the router context.
261
- */
262
- const requestToLocaleMap = /* @__PURE__ */ new WeakMap();
263
-
264
- //#endregion
265
- //#region src/site-context/locale-detection.ts
266
- /**
267
- * Read locale from cookie.
268
- */
269
- async function readLocaleFromCookie(request, cookie) {
270
- return readCookieFromRequest(request, cookie);
271
- }
272
- /**
273
- * Get locale object using the locale id or alias.
274
- * 1. Check localeIdOrAlias against each locale's alias; if matched, return that locale.
275
- * 2. Else check against each locale's id; if matched, return that locale.
276
- * 3. If no match, return null (caller should use defaultLocale).
277
- *
278
- * @param localeIdentifier - The locale id or alias to get the locale from. Null is allowed because this may come from
279
- * extrenal sources such as cookies, headers, or query parameters.
280
- * @param locales - The list of locales to search through.
281
- * @returns The locale object if found, otherwise null.
282
- */
283
- function getLocaleFromIdOrAlias(localeIdentifier, locales) {
284
- if (!localeIdentifier) return null;
285
- return locales.find((locale) => locale.alias === localeIdentifier || locale.id === localeIdentifier) ?? null;
286
- }
287
- /**
288
- * Resolve locale using the configured detection order.
289
- * Returns the first valid locale from the first source that yields a valid value.
290
- */
291
- async function resolveLocale(request, settings, site) {
292
- const { defaultLocale, localeDetectionConfig, localeCookie } = settings;
293
- const { supportedLocales } = site;
294
- let locale = null;
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;
297
- const resolvers = {
298
- path: () => Promise.resolve(lookupFromPath(requestUrl.pathname, localeDetectionConfig.lookupFromPathIndex + basePathOffset)),
299
- querystring: () => Promise.resolve(requestUrl.searchParams.get(localeDetectionConfig.lookupQuerystring)),
300
- header: () => Promise.resolve(request.headers.get(localeDetectionConfig.lookupHeader)),
301
- cookie: async () => readLocaleFromCookie(request, localeCookie)
302
- };
303
- for (const method of localeDetectionConfig.order) {
304
- const resolvedLocale = getLocaleFromIdOrAlias(await resolvers[method]?.(), supportedLocales);
305
- if (resolvedLocale) return resolvedLocale;
306
- }
307
- if (!locale) locale = getLocaleFromIdOrAlias(defaultLocale, supportedLocales);
308
- if (!locale) throw new Error(`Default locale ${defaultLocale} not found in the list of supported locales for site ${site.id}.`);
309
- return locale;
310
- }
311
-
312
- //#endregion
313
- //#region src/site-context/middleware.ts
314
- const siteContext = createContext$1(null);
315
- /**
316
- * Helper function to get site context cookies from router context.
317
- * Useful in server actions and loaders that need to read/set cookies.
318
- *
319
- * @param context - Router context provider
320
- * @returns Object with siteCookie and localeCookie instances, or null if context not set
321
- *
322
- * @example
323
- * ```typescript
324
- * export const action: ActionFunction = async ({ request, context }) => {
325
- * const cookies = getSiteContextCookies(context);
326
- * if (cookies) {
327
- * const cookieHeader = await cookies.localeCookie.serialize(locale);
328
- * // ... use cookieHeader
329
- * }
330
- * };
331
- * ```
332
- */
333
- function getSiteContextCookies(context) {
334
- const siteCtx = context.get(siteContext);
335
- if (!siteCtx) return null;
336
- return {
337
- siteCookie: siteCtx.siteCookie,
338
- localeCookie: siteCtx.localeCookie
339
- };
340
- }
341
- /**
342
- * Helper function to determine if cookies should be set based on:
343
- * 1. Whether caching is enabled for each cookie type
344
- * 2. Whether the resolved value differs from the existing cookie
345
- * 3. Whether cookies were already set by actions/loaders in the response
346
- *
347
- * @param request - Incoming request
348
- * @param response - Response from next()
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
352
- * @returns Object with shouldSetSiteCookie and shouldSetLocaleCookie booleans
353
- */
354
- async function shouldSetCookies(request, response, settings, site, locale) {
355
- const cacheSite = settings.siteDetectionConfig.caches?.includes("cookie");
356
- const cacheLocale = settings.localeDetectionConfig.caches?.includes("cookie");
357
- if (!cacheSite && !cacheLocale) return {
358
- shouldSetSiteCookie: false,
359
- shouldSetLocaleCookie: false
360
- };
361
- const responseSetCookies = response.headers.getSetCookie?.() || [];
362
- const isSettingSiteCookieInResponse = responseSetCookies.some((cookie) => cookie.startsWith(`${settings.siteCookie.name}=`));
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)]);
366
- return {
367
- shouldSetSiteCookie: cacheSite && !isSettingSiteCookieInResponse && existingSiteCookie !== site.id,
368
- shouldSetLocaleCookie: cacheLocale && !isSettingLocaleCookieInResponse && existingLocaleCookie !== locale.id
369
- };
370
- }
371
- /**
372
- * Creates a site context middleware that resolves the current site from
373
- * the request (path, cookie, header, query, or default) and stores the
374
- * result in the router context.
375
- *
376
- * Does not import or read from app config context; the consumer supplies config.
377
- */
378
- function createSiteContextMiddleware(config) {
379
- const siteDetectionConfig = {
380
- ...DEFAULT_SITE_DETECTION,
381
- ...config.siteDetectionConfig
382
- };
383
- const localeDetectionConfig = {
384
- ...DEFAULT_LOCALE_DETECTION,
385
- ...config.localeDetectionConfig
386
- };
387
- const siteCookie = createSiteContextCookie(siteDetectionConfig.lookupCookie);
388
- const localeCookie = createSiteContextCookie(localeDetectionConfig.lookupCookie);
389
- const settings = {
390
- ...config,
391
- siteDetectionConfig,
392
- localeDetectionConfig,
393
- siteCookie,
394
- localeCookie
395
- };
396
- const siteContextMiddleware = async ({ request, context }, next) => {
397
- const site = await resolveSite(request, settings);
398
- const locale = await resolveLocale(request, settings, site);
399
- context.set(siteContext, {
400
- site,
401
- locale,
402
- siteCookie: settings.siteCookie,
403
- localeCookie: settings.localeCookie
404
- });
405
- requestToLocaleMap.set(request, locale.id);
406
- const response = await next();
407
- const { shouldSetSiteCookie, shouldSetLocaleCookie } = await shouldSetCookies(request, response, settings, site, locale);
408
- if (!shouldSetSiteCookie && !shouldSetLocaleCookie) return response;
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)]);
410
- if (siteSetCookie) response.headers.append("Set-Cookie", siteSetCookie);
411
- if (localeSetCookie) response.headers.append("Set-Cookie", localeSetCookie);
412
- return response;
413
- };
414
- return siteContextMiddleware;
415
- }
416
-
417
- //#endregion
418
- export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, sanitizePrefix, siteContext, stripPathPrefix, useSite };
419
- //# sourceMappingURL=site-context.js.map
4
+ export { SiteProvider, applyUrlConfig, buildUrl, createSiteContextMiddleware, getSiteContextCookies, requestToLocaleMap, resolvePrefix, resolveSiteContext, sanitizePrefix, siteContext, stripPathPrefix, useSite };