@websolutespa/bom-next 4.0.0-next.3 → 4.0.0-next.5

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 (59) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/dist/client/extra.d.ts +3 -3
  4. package/dist/client/label.d.ts +3 -3
  5. package/dist/client/label.d.ts.map +1 -1
  6. package/dist/client/layout.d.ts +3 -3
  7. package/dist/client/metadata.d.ts +3 -3
  8. package/dist/client/page.d.ts +3 -3
  9. package/dist/client/useApi.d.ts +7 -7
  10. package/dist/client/useApi.d.ts.map +1 -1
  11. package/dist/client/useCollectionApi.d.ts +6 -6
  12. package/dist/client/user.d.ts +3 -3
  13. package/dist/exports/server.d.ts +2 -1
  14. package/dist/exports/server.js +2 -1
  15. package/dist/lib/debug.js +3 -0
  16. package/dist/lib/debug.js.map +1 -1
  17. package/dist/lib/ttl-cache.js +5 -0
  18. package/dist/lib/ttl-cache.js.map +1 -1
  19. package/dist/proxy/route-auto-detection.d.ts.map +1 -1
  20. package/dist/proxy/route-auto-detection.js +8 -1
  21. package/dist/proxy/route-auto-detection.js.map +1 -1
  22. package/dist/server/handlers/revalidate.handler.d.ts +49 -0
  23. package/dist/server/handlers/revalidate.handler.d.ts.map +1 -0
  24. package/dist/server/handlers/revalidate.handler.js +103 -0
  25. package/dist/server/handlers/revalidate.handler.js.map +1 -0
  26. package/dist/server/helpers.d.ts +1 -2
  27. package/dist/server/helpers.d.ts.map +1 -1
  28. package/dist/server/json-ld.d.ts +2 -2
  29. package/dist/server/json-ld.d.ts.map +1 -1
  30. package/dist/server/json-ld.js +6 -0
  31. package/dist/server/json-ld.js.map +1 -1
  32. package/dist/server/metadata.d.ts +6 -3
  33. package/dist/server/metadata.d.ts.map +1 -1
  34. package/dist/server/metadata.js +11 -5
  35. package/dist/server/metadata.js.map +1 -1
  36. package/dist/server/mixer.d.ts +5 -3
  37. package/dist/server/mixer.d.ts.map +1 -1
  38. package/dist/server/mixer.js +9 -8
  39. package/dist/server/mixer.js.map +1 -1
  40. package/dist/types.d.ts +6 -3
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +4 -4
  43. package/src/client/user.test.tsx +44 -44
  44. package/src/exports/server.ts +1 -0
  45. package/src/lib/debug.ts +11 -11
  46. package/src/lib/route.test.ts +53 -53
  47. package/src/lib/ttl-cache.ts +72 -72
  48. package/src/proxy/route-auto-detection.test.ts +55 -50
  49. package/src/proxy/route-auto-detection.ts +11 -1
  50. package/src/server/context.test.ts +31 -7
  51. package/src/server/context.type-test.ts +106 -106
  52. package/src/server/handlers/revalidate.handler.ts +139 -0
  53. package/src/server/handlers/route.handler.test.ts +39 -39
  54. package/src/server/json-ld.tsx +51 -51
  55. package/src/server/metadata.ts +7 -7
  56. package/src/server/mixer.ts +7 -13
  57. package/src/server/request-data.test.ts +37 -0
  58. package/src/server/session/session.ts +22 -17
  59. package/src/types.ts +6 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @websolutespa/bom-next
2
2
 
3
+ ## 4.0.0-next.5
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: renamed API handlers for image, mock, preview, revalidate, vars, and session endpoints
8
+ - Updated dependencies
9
+ - @websolutespa/payload-plugin-bowl@4.0.0-next.4
10
+
11
+ ## 4.0.0-next.4
12
+
13
+ ### Patch Changes
14
+
15
+ - Helpers no longer read request data; only the template does and passes it down.
16
+ - `getPageMetadata` / `getMetadata` read only `params` (no more `DYNAMIC_SERVER_USAGE` on static/ISR pages). A dynamic template awaits its own `searchParams` and calls `buildPageMetadata(page, layout, { searchParams })`.
17
+ - `getPayload` no longer calls `headers()`: in draft mode pass `headers` from the template.
18
+ - `draft` defaults to `false` in `createPageContext` and `createLayoutContext`; `'auto'` (reads `draftMode()`) is an explicit opt-in.
19
+ - Session helpers (`createSession`, `getSession`, `hasValidSession`, `updateSession`, `deleteSession`) take the cookie store as an argument.
20
+ - `detectedRoute` cookie values with dot segments (`..`, also percent-encoded) or backslashes are rejected.
21
+
22
+ - Updated dependencies
23
+ - @websolutespa/payload-plugin-bowl@4.0.0-next.3
24
+ - @websolutespa/payload-db-memory@3.0.3
25
+ - @websolutespa/payload-utils@4.0.0-next.0
26
+ - @websolutespa/bom-core@4.0.0-next.1
27
+
3
28
  ## 4.0.0-next.3
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -86,4 +86,4 @@ Per-request diagnostics (proxy, local API calls, context cache) are logged in de
86
86
 
87
87
  ## User
88
88
 
89
- `UserProvider` reads `/api/auth/me` once per browser session, shared by every mount. Pass `initialUser` (a user or `null`) when the server already knows it to skip the request; `setUser` and `invalidateUser()` keep the cached value current after login or logout.
89
+ `UserProvider` reads `/api/auth/me` once per page load, shared by every mount and client navigation. Pass `initialUser` (a user or `null`) when the server already knows it to skip the request; `setUser` and `invalidateUser()` keep the cached value current after login or logout.
@@ -1,15 +1,15 @@
1
- import * as react1 from "react";
1
+ import * as react0 from "react";
2
2
 
3
3
  //#region src/client/extra.d.ts
4
4
  type IExtraContext = Record<string, any>;
5
- declare const ExtraContext: react1.Context<IExtraContext | undefined>;
5
+ declare const ExtraContext: react0.Context<IExtraContext | undefined>;
6
6
  declare function ExtraProvider({
7
7
  extra,
8
8
  children
9
9
  }: {
10
10
  extra?: Record<string, any>;
11
11
  children?: React.ReactNode;
12
- }): react1.JSX.Element;
12
+ }): react0.JSX.Element;
13
13
  //# sourceMappingURL=extra.d.ts.map
14
14
 
15
15
  //#endregion
@@ -1,13 +1,13 @@
1
- import * as react3 from "react";
1
+ import * as react21 from "react";
2
2
 
3
3
  //#region src/client/label.d.ts
4
4
  type ILabelContext = (key: string, params?: Record<string, any>) => string;
5
- declare const LabelContext: react3.Context<ILabelContext | undefined>;
5
+ declare const LabelContext: react21.Context<ILabelContext | undefined>;
6
6
  declare function LabelProvider({
7
7
  children
8
8
  }: {
9
9
  children?: React.ReactNode;
10
- }): react3.JSX.Element;
10
+ }): react21.JSX.Element;
11
11
  //# sourceMappingURL=label.d.ts.map
12
12
 
13
13
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"label.d.ts","names":[],"sources":["../../src/client/label.tsx"],"sourcesContent":[],"mappings":";;;KAMY,aAAA,0BAAuC;cAEtC,cAAY,MAAA,CAAA,QAAA;iBAET,aAAA;;AAFhB;EAFY,QAAA,CAAA,EAI6C,KAAA,CAAM,SAJtC;AAEzB,CAAA,CAAA,EAE0E,MAAA,CAAA,GAAA,CAAA,OAFK"}
1
+ {"version":3,"file":"label.d.ts","names":[],"sources":["../../src/client/label.tsx"],"sourcesContent":[],"mappings":";;;KAMY,aAAA,0BAAuC;cAEtC,cAAY,OAAA,CAAA,QAAA;iBAET,aAAA;;AAFhB;EAFY,QAAA,CAAA,EAI6C,KAAA,CAAM,SAJtC;AAEzB,CAAA,CAAA,EAE0E,OAAA,CAAA,GAAA,CAAA,OAFK"}
@@ -1,16 +1,16 @@
1
1
  import { ILayout } from "@websolutespa/bom-core";
2
- import * as react9 from "react";
2
+ import * as react2 from "react";
3
3
 
4
4
  //#region src/client/layout.d.ts
5
5
  type ILayoutContext = ILayout;
6
- declare const LayoutContext: react9.Context<ILayoutContext | undefined>;
6
+ declare const LayoutContext: react2.Context<ILayoutContext | undefined>;
7
7
  declare function LayoutProvider({
8
8
  layout,
9
9
  children
10
10
  }: {
11
11
  layout: ILayout;
12
12
  children?: React.ReactNode;
13
- }): react9.JSX.Element;
13
+ }): react2.JSX.Element;
14
14
  //# sourceMappingURL=layout.d.ts.map
15
15
 
16
16
  //#endregion
@@ -2,7 +2,7 @@ import { OpenGraphType } from "../lib/open-graph.js";
2
2
  import { StructuredDataType } from "../lib/structured-data.js";
3
3
  import { TwitterCardType } from "../lib/twitter-card.js";
4
4
  import { IMetaData } from "../lib/meta-data.js";
5
- import * as react5 from "react";
5
+ import * as react4 from "react";
6
6
  import { ReactNode } from "react";
7
7
 
8
8
  //#region src/client/metadata.d.ts
@@ -29,7 +29,7 @@ type IMetaDataOptions = {
29
29
  twitterCard?: TwitterCardType;
30
30
  };
31
31
  type IMetaDataContext = IMetaData;
32
- declare const MetaDataContext: react5.Context<IMetaData | undefined>;
32
+ declare const MetaDataContext: react4.Context<IMetaData | undefined>;
33
33
  /**
34
34
  * Computes page metadata for client components (`useMetaData`). It renders
35
35
  * nothing into `<head>`: in the App Router, export `generateMetadata` built
@@ -46,7 +46,7 @@ declare function MetaDataProvider({
46
46
  metaData: IMetaDataOptions;
47
47
  search?: string;
48
48
  children?: ReactNode;
49
- }): react5.JSX.Element;
49
+ }): react4.JSX.Element;
50
50
  //# sourceMappingURL=metadata.d.ts.map
51
51
  //#endregion
52
52
  export { IMetaDataContext, IMetaDataOptions, MetaDataContext, MetaDataProvider };
@@ -1,10 +1,10 @@
1
1
  import * as _websolutespa_bom_core0 from "@websolutespa/bom-core";
2
2
  import { ILocalized, IPage } from "@websolutespa/bom-core";
3
- import * as react7 from "react";
3
+ import * as react6 from "react";
4
4
 
5
5
  //#region src/client/page.d.ts
6
6
  type IPageContext = ILocalized<IPage>;
7
- declare const PageContext: react7.Context<{
7
+ declare const PageContext: react6.Context<{
8
8
  [x: string]: unknown;
9
9
  id: _websolutespa_bom_core0.IEquatable;
10
10
  schema: string;
@@ -398,7 +398,7 @@ declare function PageProvider({
398
398
  }: {
399
399
  page: ILocalized<IPage>;
400
400
  children?: React.ReactNode;
401
- }): react7.JSX.Element;
401
+ }): react6.JSX.Element;
402
402
  //# sourceMappingURL=page.d.ts.map
403
403
  //#endregion
404
404
  export { IPageContext, PageContext, PageProvider };
@@ -1,13 +1,13 @@
1
1
  import { ApiRequestOptions } from "../lib/http.js";
2
- import * as react11 from "react";
2
+ import * as react8 from "react";
3
3
 
4
4
  //#region src/client/useApi.d.ts
5
- declare function useApiFetch<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
6
- declare function useApiGet<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
7
- declare function useApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
8
- declare function useApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
9
- declare function useApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
10
- declare function useApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react11.Dispatch<any>];
5
+ declare function useApiFetch<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
6
+ declare function useApiGet<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
7
+ declare function useApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
8
+ declare function useApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
9
+ declare function useApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
10
+ declare function useApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react8.Dispatch<any>];
11
11
  type Api = {
12
12
  get: <T = unknown>(pathname: string, options?: ApiRequestOptions) => Promise<T>;
13
13
  post: <T = unknown>(pathname: string, payload?: unknown, options?: ApiRequestOptions) => Promise<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"useApi.d.ts","names":[],"sources":["../../src/client/useApi.ts"],"sourcesContent":[],"mappings":";;;;iBAIgB,qDAAoD,qBAAsB,6BAAA,OAAA,CAAA;iBAM1E,mDAAkD,qBAAsB,6BAAA,OAAA,CAAA;iBAIxE,sEAAqE,qBAAsB,6BAAA,OAAA,CAAA;AAV3F,iBAcA,SAdW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAcyD,iBAdzD,CAAA,EAAA,CAc+E,CAd/E,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAc+E,OAAA,CAAA,QAd/E,CAAA,GAAA,CAAA,CAAA;AAAA,iBAkBX,WAlBW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAkB2D,iBAlB3D,CAAA,EAAA,CAkBiF,CAlBjF,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAkBiF,OAAA,CAAA,QAlBjF,CAAA,GAAA,CAAA,CAAA;AAAyC,iBAsBpD,YAtBoD,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAsBC,iBAtBD,CAAA,EAAA,CAsBuB,CAtBvB,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAsBuB,OAAA,CAAA,QAtBvB,CAAA,GAAA,CAAA,CAAA;KA0B/D,GAAA,GA1BqF;EAAA,GAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EA2BzC,iBA3ByC,EAAA,GA2BnB,OA3BmB,CA2BX,CA3BW,CAAA;EAAA,IAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EA4BrB,iBA5BqB,EAAA,GA4BC,OA5BD,CA4BS,CA5BT,CAAA;EAM1E,GAAA,EAAA,CAAA,IAAA,OAAS,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAuB2C,iBAvB3C,EAAA,GAuBiE,OAvBjE,CAuByE,CAvBzE,CAAA;EAAA,KAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAwB6C,iBAxB7C,EAAA,GAwBmE,OAxBnE,CAwB2E,CAxB3E,CAAA;EAAA,MAAyC,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAyBd,iBAzBc,EAAA,GAyBQ,OAzBR,CAyBgB,CAzBhB,CAAA;CAAsB;AAAA,iBA4BxE,MAAA,CAAA,CA5BwE,EA4B9D,GA5B8D"}
1
+ {"version":3,"file":"useApi.d.ts","names":[],"sources":["../../src/client/useApi.ts"],"sourcesContent":[],"mappings":";;;;iBAIgB,qDAAoD,qBAAsB,6BAAA,MAAA,CAAA;iBAM1E,mDAAkD,qBAAsB,6BAAA,MAAA,CAAA;iBAIxE,sEAAqE,qBAAsB,6BAAA,MAAA,CAAA;AAV3F,iBAcA,SAdW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAcyD,iBAdzD,CAAA,EAAA,CAc+E,CAd/E,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAc+E,MAAA,CAAA,QAd/E,CAAA,GAAA,CAAA,CAAA;AAAA,iBAkBX,WAlBW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAkB2D,iBAlB3D,CAAA,EAAA,CAkBiF,CAlBjF,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAkBiF,MAAA,CAAA,QAlBjF,CAAA,GAAA,CAAA,CAAA;AAAyC,iBAsBpD,YAtBoD,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAsBC,iBAtBD,CAAA,EAAA,CAsBuB,CAtBvB,GAAA,SAAA,EAAA,OAAA,EAAA,GAAA,EAsBuB,MAAA,CAAA,QAtBvB,CAAA,GAAA,CAAA,CAAA;KA0B/D,GAAA,GA1BqF;EAAA,GAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EA2BzC,iBA3ByC,EAAA,GA2BnB,OA3BmB,CA2BX,CA3BW,CAAA;EAAA,IAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EA4BrB,iBA5BqB,EAAA,GA4BC,OA5BD,CA4BS,CA5BT,CAAA;EAM1E,GAAA,EAAA,CAAA,IAAA,OAAS,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAuB2C,iBAvB3C,EAAA,GAuBiE,OAvBjE,CAuByE,CAvBzE,CAAA;EAAA,KAAA,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAwB6C,iBAxB7C,EAAA,GAwBmE,OAxBnE,CAwB2E,CAxB3E,CAAA;EAAA,MAAyC,EAAA,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAyBd,iBAzBc,EAAA,GAyBQ,OAzBR,CAyBgB,CAzBhB,CAAA;CAAsB;AAAA,iBA4BxE,MAAA,CAAA,CA5BwE,EA4B9D,GA5B8D"}
@@ -1,6 +1,6 @@
1
1
  import { ApiRequestOptions } from "../lib/http.js";
2
2
  import { QueryParams } from "@websolutespa/bom-core";
3
- import * as react17 from "react";
3
+ import * as react14 from "react";
4
4
 
5
5
  //#region src/client/useCollectionApi.d.ts
6
6
  type CollectionApi = {
@@ -11,11 +11,11 @@ type CollectionApi = {
11
11
  delete: <T = unknown>(pathname: string, options?: ApiRequestOptions) => Promise<T>;
12
12
  };
13
13
  declare function useCollectionApi(): CollectionApi;
14
- declare function useCollectionApiGet<T = unknown>(pathname: string, params?: QueryParams, options?: ApiRequestOptions): [T | undefined, boolean, any, react17.Dispatch<any>];
15
- declare function useCollectionApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react17.Dispatch<any>];
16
- declare function useCollectionApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react17.Dispatch<any>];
17
- declare function useCollectionApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react17.Dispatch<any>];
18
- declare function useCollectionApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react17.Dispatch<any>];
14
+ declare function useCollectionApiGet<T = unknown>(pathname: string, params?: QueryParams, options?: ApiRequestOptions): [T | undefined, boolean, any, react14.Dispatch<any>];
15
+ declare function useCollectionApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react14.Dispatch<any>];
16
+ declare function useCollectionApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react14.Dispatch<any>];
17
+ declare function useCollectionApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react14.Dispatch<any>];
18
+ declare function useCollectionApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react14.Dispatch<any>];
19
19
  //#endregion
20
20
  export { useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useCollectionApiPatch, useCollectionApiPost, useCollectionApiPut };
21
21
  //# sourceMappingURL=useCollectionApi.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { IEndUser, IUser } from "../types.js";
2
- import * as react22 from "react";
2
+ import * as react19 from "react";
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  //#region src/client/user.d.ts
@@ -8,7 +8,7 @@ type UserContextType = {
8
8
  user?: UserValue;
9
9
  setUser: (user?: UserValue) => void;
10
10
  };
11
- declare const UserContext: react22.Context<UserContextType>;
11
+ declare const UserContext: react19.Context<UserContextType>;
12
12
  /** Forces the next UserProvider mount to read `/auth/me` again. */
13
13
  declare function invalidateUser(): void;
14
14
  /**
@@ -21,7 +21,7 @@ declare function UserProvider({
21
21
  }: {
22
22
  initialUser?: UserValue;
23
23
  children: ReactNode;
24
- }): react22.JSX.Element;
24
+ }): react19.JSX.Element;
25
25
  //#endregion
26
26
  export { UserContext, UserContextType, UserProvider, invalidateUser };
27
27
  //# sourceMappingURL=user.d.ts.map
@@ -5,6 +5,7 @@ import { collectionDeleteHandler, collectionGetHandler, collectionPatchHandler,
5
5
  import { indexGetHandler } from "../server/handlers/index.handler.js";
6
6
  import { previewHandler } from "../server/handlers/preview.handler.js";
7
7
  import { getRegistry, getRegistryItem } from "../server/handlers/registry.handler.js";
8
+ import { CdnPurgeHandler, cloudflarePurgeHandler, getRevalidateHandler, resolveCdnPurgeHandler } from "../server/handlers/revalidate.handler.js";
8
9
  import { getCompiledRedirects, getRouteHandler } from "../server/handlers/route.handler.js";
9
10
  import { ISiteMap, getSiteMapIndex, getSiteMapXML, getSiteMapXSL } from "../server/handlers/sitemap.handler.js";
10
11
  import { getStaticParams, getStaticParamsFromPayload, getStaticParams_ } from "../server/handlers/static-params.handler.js";
@@ -19,4 +20,4 @@ import { buildBaseOptions, buildReadOptions, clearMixerContextCache, filterRoute
19
20
  import { PAGE_ENVELOPE_FIELDS, getPage, withEnvelopeSelect } from "../server/page.js";
20
21
  import { getHrefBeforeRedirect, getNextUrl } from "../server/utils.js";
21
22
  import { getUtmz } from "../server/utmz.js";
22
- export { GetPageMetadataOptions, ISiteMap, JsonLd, PAGE_ENVELOPE_FIELDS, PageMetadataBrand, PageMetadataContext, PageMetadataOptions, PageMetadataProps, PageMetadataSearchParams, PageStructuredDataOptions, buildBaseOptions, buildPageMetadata, buildPageStructuredData, buildReadOptions, clearMixerContextCache, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getCompiledRedirects, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageAlternates, getPageContext, getPageMetadata, getPageXDefault, getPaginationQuery, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, inheritParentMetadata, previewHandler, resolveDraft, resolveMetadataUrl, setContext, setPageContext, stableStringify, toPlainText, withEnvelopeSelect, withLazyProps };
23
+ export { CdnPurgeHandler, GetPageMetadataOptions, ISiteMap, JsonLd, PAGE_ENVELOPE_FIELDS, PageMetadataBrand, PageMetadataContext, PageMetadataOptions, PageMetadataProps, PageMetadataSearchParams, PageStructuredDataOptions, buildBaseOptions, buildPageMetadata, buildPageStructuredData, buildReadOptions, clearMixerContextCache, cloudflarePurgeHandler, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getCompiledRedirects, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageAlternates, getPageContext, getPageMetadata, getPageXDefault, getPaginationQuery, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRevalidateHandler, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, inheritParentMetadata, previewHandler, resolveCdnPurgeHandler, resolveDraft, resolveMetadataUrl, setContext, setPageContext, stableStringify, toPlainText, withEnvelopeSelect, withLazyProps };
@@ -10,6 +10,7 @@ import { collectionDeleteHandler, collectionGetHandler, collectionPatchHandler,
10
10
  import { indexGetHandler } from "../server/handlers/index.handler.js";
11
11
  import { previewHandler } from "../server/handlers/preview.handler.js";
12
12
  import { getRegistry, getRegistryItem } from "../server/handlers/registry.handler.js";
13
+ import { cloudflarePurgeHandler, getRevalidateHandler, resolveCdnPurgeHandler } from "../server/handlers/revalidate.handler.js";
13
14
  import { getCompiledRedirects, getRouteHandler } from "../server/handlers/route.handler.js";
14
15
  import { getSiteMapIndex, getSiteMapXML, getSiteMapXSL } from "../server/handlers/sitemap.handler.js";
15
16
  import { getStaticParams, getStaticParamsFromPayload, getStaticParams_ } from "../server/handlers/static-params.handler.js";
@@ -20,4 +21,4 @@ import { JsonLd, buildPageStructuredData } from "../server/json-ld.js";
20
21
  import { decorateLazyComponents, withLazyProps } from "../server/lazy.js";
21
22
  import { getUtmz } from "../server/utmz.js";
22
23
 
23
- export { JsonLd, PAGE_ENVELOPE_FIELDS, buildBaseOptions, buildPageMetadata, buildPageStructuredData, buildReadOptions, clearMixerContextCache, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getCompiledRedirects, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageAlternates, getPageContext, getPageMetadata, getPageXDefault, getPaginationQuery, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, inheritParentMetadata, previewHandler, resolveDraft, resolveMetadataUrl, setContext, setPageContext, stableStringify, toPlainText, withEnvelopeSelect, withLazyProps };
24
+ export { JsonLd, PAGE_ENVELOPE_FIELDS, buildBaseOptions, buildPageMetadata, buildPageStructuredData, buildReadOptions, clearMixerContextCache, cloudflarePurgeHandler, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getCompiledRedirects, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageAlternates, getPageContext, getPageMetadata, getPageXDefault, getPaginationQuery, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRevalidateHandler, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, inheritParentMetadata, previewHandler, resolveCdnPurgeHandler, resolveDraft, resolveMetadataUrl, setContext, setPageContext, stableStringify, toPlainText, withEnvelopeSelect, withLazyProps };
package/dist/lib/debug.js CHANGED
@@ -2,8 +2,11 @@ import { isDevelopment } from "@websolutespa/bom-core";
2
2
 
3
3
  //#region src/lib/debug.ts
4
4
  /**
5
+
5
6
  * Per-request diagnostics (proxy, local API calls, context cache). They run
7
+
6
8
  * on every page view and prefetch, so they are logged in development only.
9
+
7
10
  */
8
11
  function debug(...args) {
9
12
  if (isDevelopment) console.log(...args);
@@ -1 +1 @@
1
- {"version":3,"file":"debug.js","names":[],"sources":["../../src/lib/debug.ts"],"sourcesContent":["import { isDevelopment } from '@websolutespa/bom-core';\n\n/**\n * Per-request diagnostics (proxy, local API calls, context cache). They run\n * on every page view and prefetch, so they are logged in development only.\n */\nexport function debug(...args: unknown[]): void {\n if (isDevelopment) {\n console.log(...args);\n }\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,MAAM,GAAG,MAAuB;AAC9C,KAAI,eACF,QAAQ,IAAI,GAAG,KAAK;AAEvB"}
1
+ {"version":3,"file":"debug.js","names":[],"sources":["../../src/lib/debug.ts"],"sourcesContent":["import { isDevelopment } from '@websolutespa/bom-core';\r\n\r\n/**\r\n * Per-request diagnostics (proxy, local API calls, context cache). They run\r\n * on every page view and prefetch, so they are logged in development only.\r\n */\r\nexport function debug(...args: unknown[]): void {\r\n if (isDevelopment) {\r\n console.log(...args);\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AAMA,SAAgB,MAAM,GAAG,MAAuB;AAC9C,KAAI,eACF,QAAQ,IAAI,GAAG,KAAK;AAEvB"}
@@ -1,9 +1,14 @@
1
1
  //#region src/lib/ttl-cache.ts
2
2
  /**
3
+
3
4
  * Small per-instance TTL cache with LRU eviction and in-flight de-duplication.
5
+
4
6
  *
7
+
5
8
  * Values are stored as promises so concurrent requests for the same key share
9
+
6
10
  * one upstream call. Rejected promises are evicted immediately.
11
+
7
12
  */
8
13
  var TtlCache = class {
9
14
  entries = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"ttl-cache.js","names":["maxSize: number","key: string","ttl: (value: T) => number","factory: () => Promise<T>","pending: TtlCacheEntry<T>","entry: TtlCacheEntry<T>"],"sources":["../../src/lib/ttl-cache.ts"],"sourcesContent":["type TtlCacheEntry<T> = {\n value: Promise<T>;\n expires: number;\n};\n\n/**\n * Small per-instance TTL cache with LRU eviction and in-flight de-duplication.\n *\n * Values are stored as promises so concurrent requests for the same key share\n * one upstream call. Rejected promises are evicted immediately.\n */\nexport class TtlCache<T> {\n private readonly entries = new Map<string, TtlCacheEntry<T>>();\n\n constructor(private readonly maxSize: number) { }\n\n get size() {\n return this.entries.size;\n }\n\n async getOrSet(key: string, ttl: (value: T) => number, factory: () => Promise<T>): Promise<T> {\n const now = Date.now();\n const entry = this.entries.get(key);\n if (entry && entry.expires > now) {\n // Re-insert to mark the entry as most recently used.\n this.entries.delete(key);\n this.entries.set(key, entry);\n return entry.value;\n }\n if (entry) {\n this.entries.delete(key);\n }\n const value = factory();\n // Until the value settles, keep it long enough to de-duplicate concurrent calls.\n const pending: TtlCacheEntry<T> = { value, expires: Number.POSITIVE_INFINITY };\n this.set(key, pending);\n try {\n const resolved = await value;\n const duration = ttl(resolved);\n if (duration > 0 && this.entries.get(key) === pending) {\n pending.expires = Date.now() + duration;\n } else if (this.entries.get(key) === pending) {\n this.entries.delete(key);\n }\n return resolved;\n } catch (error) {\n if (this.entries.get(key) === pending) {\n this.entries.delete(key);\n }\n throw error;\n }\n }\n\n delete(key: string) {\n this.entries.delete(key);\n }\n\n clear() {\n this.entries.clear();\n }\n\n private set(key: string, entry: TtlCacheEntry<T>) {\n if (this.entries.size >= this.maxSize) {\n // Maps iterate in insertion order, so the first key is the least recently used.\n const oldest = this.entries.keys().next().value;\n if (oldest !== undefined) {\n this.entries.delete(oldest);\n }\n }\n this.entries.set(key, entry);\n }\n}\n"],"mappings":";;;;;;;AAWA,IAAa,WAAb,MAAyB;CACvB,AAAiB,0BAAU,IAAI;CAE/B,YAA6BA,SAAiB;EAAjB;CAAoB;CAEjD,IAAI,OAAO;AACT,SAAO,KAAK,QAAQ;CACrB;CAED,MAAM,SAASC,KAAaC,KAA2BC,SAAuC;EAC5F,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,MAAI,SAAS,MAAM,UAAU,KAAK;GAEhC,KAAK,QAAQ,OAAO,IAAI;GACxB,KAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,UAAO,MAAM;EACd;AACD,MAAI,OACF,KAAK,QAAQ,OAAO,IAAI;EAE1B,MAAM,QAAQ,SAAS;EAEvB,MAAMC,UAA4B;GAAE;GAAO,SAAS,OAAO;EAAmB;EAC9E,KAAK,IAAI,KAAK,QAAQ;AACtB,MAAI;GACF,MAAM,WAAW,MAAM;GACvB,MAAM,WAAW,IAAI,SAAS;AAC9B,OAAI,WAAW,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAC5C,QAAQ,UAAU,KAAK,KAAK,GAAG;YACtB,KAAK,QAAQ,IAAI,IAAI,KAAK,SACnC,KAAK,QAAQ,OAAO,IAAI;AAE1B,UAAO;EACR,SAAQ,OAAO;AACd,OAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAC5B,KAAK,QAAQ,OAAO,IAAI;AAE1B,SAAM;EACP;CACF;CAED,OAAOH,KAAa;EAClB,KAAK,QAAQ,OAAO,IAAI;CACzB;CAED,QAAQ;EACN,KAAK,QAAQ,OAAO;CACrB;CAED,AAAQ,IAAIA,KAAaI,OAAyB;AAChD,MAAI,KAAK,QAAQ,QAAQ,KAAK,SAAS;GAErC,MAAM,SAAS,KAAK,QAAQ,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,WAAW,QACb,KAAK,QAAQ,OAAO,OAAO;EAE9B;EACD,KAAK,QAAQ,IAAI,KAAK,MAAM;CAC7B;AACF"}
1
+ {"version":3,"file":"ttl-cache.js","names":["maxSize: number","key: string","ttl: (value: T) => number","factory: () => Promise<T>","pending: TtlCacheEntry<T>","entry: TtlCacheEntry<T>"],"sources":["../../src/lib/ttl-cache.ts"],"sourcesContent":["type TtlCacheEntry<T> = {\r\n value: Promise<T>;\r\n expires: number;\r\n};\r\n\r\n/**\r\n * Small per-instance TTL cache with LRU eviction and in-flight de-duplication.\r\n *\r\n * Values are stored as promises so concurrent requests for the same key share\r\n * one upstream call. Rejected promises are evicted immediately.\r\n */\r\nexport class TtlCache<T> {\r\n private readonly entries = new Map<string, TtlCacheEntry<T>>();\r\n\r\n constructor(private readonly maxSize: number) { }\r\n\r\n get size() {\r\n return this.entries.size;\r\n }\r\n\r\n async getOrSet(key: string, ttl: (value: T) => number, factory: () => Promise<T>): Promise<T> {\r\n const now = Date.now();\r\n const entry = this.entries.get(key);\r\n if (entry && entry.expires > now) {\r\n // Re-insert to mark the entry as most recently used.\r\n this.entries.delete(key);\r\n this.entries.set(key, entry);\r\n return entry.value;\r\n }\r\n if (entry) {\r\n this.entries.delete(key);\r\n }\r\n const value = factory();\r\n // Until the value settles, keep it long enough to de-duplicate concurrent calls.\r\n const pending: TtlCacheEntry<T> = { value, expires: Number.POSITIVE_INFINITY };\r\n this.set(key, pending);\r\n try {\r\n const resolved = await value;\r\n const duration = ttl(resolved);\r\n if (duration > 0 && this.entries.get(key) === pending) {\r\n pending.expires = Date.now() + duration;\r\n } else if (this.entries.get(key) === pending) {\r\n this.entries.delete(key);\r\n }\r\n return resolved;\r\n } catch (error) {\r\n if (this.entries.get(key) === pending) {\r\n this.entries.delete(key);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n delete(key: string) {\r\n this.entries.delete(key);\r\n }\r\n\r\n clear() {\r\n this.entries.clear();\r\n }\r\n\r\n private set(key: string, entry: TtlCacheEntry<T>) {\r\n if (this.entries.size >= this.maxSize) {\r\n // Maps iterate in insertion order, so the first key is the least recently used.\r\n const oldest = this.entries.keys().next().value;\r\n if (oldest !== undefined) {\r\n this.entries.delete(oldest);\r\n }\r\n }\r\n this.entries.set(key, entry);\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,WAAb,MAAyB;CACvB,AAAiB,0BAAU,IAAI;CAE/B,YAA6BA,SAAiB;EAAjB;CAAoB;CAEjD,IAAI,OAAO;AACT,SAAO,KAAK,QAAQ;CACrB;CAED,MAAM,SAASC,KAAaC,KAA2BC,SAAuC;EAC5F,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,MAAI,SAAS,MAAM,UAAU,KAAK;GAEhC,KAAK,QAAQ,OAAO,IAAI;GACxB,KAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,UAAO,MAAM;EACd;AACD,MAAI,OACF,KAAK,QAAQ,OAAO,IAAI;EAE1B,MAAM,QAAQ,SAAS;EAEvB,MAAMC,UAA4B;GAAE;GAAO,SAAS,OAAO;EAAmB;EAC9E,KAAK,IAAI,KAAK,QAAQ;AACtB,MAAI;GACF,MAAM,WAAW,MAAM;GACvB,MAAM,WAAW,IAAI,SAAS;AAC9B,OAAI,WAAW,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAC5C,QAAQ,UAAU,KAAK,KAAK,GAAG;YACtB,KAAK,QAAQ,IAAI,IAAI,KAAK,SACnC,KAAK,QAAQ,OAAO,IAAI;AAE1B,UAAO;EACR,SAAQ,OAAO;AACd,OAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAC5B,KAAK,QAAQ,OAAO,IAAI;AAE1B,SAAM;EACP;CACF;CAED,OAAOH,KAAa;EAClB,KAAK,QAAQ,OAAO,IAAI;CACzB;CAED,QAAQ;EACN,KAAK,QAAQ,OAAO;CACrB;CAED,AAAQ,IAAIA,KAAaI,OAAyB;AAChD,MAAI,KAAK,QAAQ,QAAQ,KAAK,SAAS;GAErC,MAAM,SAAS,KAAK,QAAQ,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,WAAW,QACb,KAAK,QAAQ,OAAO,OAAO;EAE9B;EACD,KAAK,QAAQ,IAAI,KAAK,MAAM;CAC7B;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-auto-detection.d.ts","names":[],"sources":["../../src/proxy/route-auto-detection.ts"],"sourcesContent":[],"mappings":";;;;iBAMsB,YAAA,UAAsB,sCAAmC;;AAA/E;;;AAA+E,iBA2C/D,oBAAA,CA3C+D,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,KAAA,IAAA,MAAA;AAAA,iBA+CzD,aAAA,CA/CyD,OAAA,EA+ClC,WA/CkC,EAAA,aAAA,CAAA,EAAA,MAAA,CAAA,EA+CC,OA/CD,CAAA,MAAA,GAAA,SAAA,CAAA;AA2C/D,cAgDH,kBAhDuB,EAgDH,UAhDG;AAIpC"}
1
+ {"version":3,"file":"route-auto-detection.d.ts","names":[],"sources":["../../src/proxy/route-auto-detection.ts"],"sourcesContent":[],"mappings":";;;;iBAMsB,YAAA,UAAsB,sCAAmC;;AAA/E;;;AAA+E,iBA2C/D,oBAAA,CA3C+D,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,KAAA,IAAA,MAAA;AAAA,iBAyDzD,aAAA,CAzDyD,OAAA,EAyDlC,WAzDkC,EAAA,aAAA,CAAA,EAAA,MAAA,CAAA,EAyDC,OAzDD,CAAA,MAAA,GAAA,SAAA,CAAA;AA2C/D,cA0DH,kBA1DuB,EA0DH,UA1DG;AAcpC"}
@@ -42,7 +42,14 @@ function setCachedCountry(ip, country) {
42
42
 
43
43
  */
44
44
  function isValidDetectedRoute(value) {
45
- return Boolean(value && value.startsWith("/") && !value.startsWith("//") && value !== "/");
45
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value === "/" || value.includes("\\")) return false;
46
+ let decoded;
47
+ try {
48
+ decoded = decodeURIComponent(value);
49
+ } catch {
50
+ return false;
51
+ }
52
+ return !decoded.split(/[/?#]/).some((segment) => segment === "." || segment === "..") && !decoded.includes("\\");
46
53
  }
47
54
  async function detectCountry(request, defaultMarket$1) {
48
55
  let ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip");
@@ -1 +1 @@
1
- {"version":3,"file":"route-auto-detection.js","names":["request: NextRequest","defaultLocale?: string","defaultLocale","ip: string","country: string","value?: string","defaultMarket?: string","defaultMarket","routeAutoDetection: Middleware","redirectUrl: string | undefined","rootRoutes: IRoute[]","markets: IMarket[]","countries: string[]","x","market","origin: string","redirectUrl: string"],"sources":["../../src/proxy/route-auto-detection.ts"],"sourcesContent":["import { asEquatable, defaultLocale, defaultMarket, IMarket, IRoute } from '@websolutespa/bom-core';\r\nimport { NextRequest, NextResponse } from 'next/server';\r\nimport { debug } from '../lib/debug';\r\nimport { localApiGet } from '../lib/http';\r\nimport { getRequestOrigin, Middleware, MiddlewareOptions } from './proxy';\r\n\r\nexport async function detectLocale(request: NextRequest, defaultLocale?: string) {\r\n const acceptLanguage = request.headers.get('accept-language')?.split(',')[0];\r\n const detectedLocale = request.cookies.get('NEXT_LOCALE')?.value || acceptLanguage || defaultLocale || process.env.DEFAULT_LOCALE;\r\n return detectedLocale as string;\r\n}\r\n\r\nconst GEOIP_TIMEOUT = 2000;\r\nconst GEOIP_CACHE_TTL = 60 * 60 * 1000;\r\nconst GEOIP_CACHE_MAX = 1000;\r\n\r\ntype GeoIpCacheEntry = { country: string; expires: number };\r\nconst globalForGeoIp = globalThis as typeof globalThis & { _bomGeoIpCache?: Map<string, GeoIpCacheEntry> };\r\n// Country lookups are external calls; cache them per IP so repeat visitors\r\n// and crawlers do not hit the GeoIP service on every request to `/`.\r\nconst geoIpCache = globalForGeoIp._bomGeoIpCache ??= new Map<string, GeoIpCacheEntry>();\r\n\r\nfunction getCachedCountry(ip: string): string | undefined {\r\n const entry = geoIpCache.get(ip);\r\n if (!entry) {\r\n return undefined;\r\n }\r\n if (entry.expires < Date.now()) {\r\n geoIpCache.delete(ip);\r\n return undefined;\r\n }\r\n return entry.country;\r\n}\r\n\r\nfunction setCachedCountry(ip: string, country: string) {\r\n if (geoIpCache.size >= GEOIP_CACHE_MAX) {\r\n // Maps iterate in insertion order, so the first key is the oldest entry.\r\n const oldest = geoIpCache.keys().next().value;\r\n if (oldest !== undefined) {\r\n geoIpCache.delete(oldest);\r\n }\r\n }\r\n geoIpCache.set(ip, { country, expires: Date.now() + GEOIP_CACHE_TTL });\r\n}\r\n\r\n/**\r\n * A relative, same-origin path previously set by this middleware. Anything\r\n * else (absolute or protocol-relative URLs) is ignored to avoid open redirects.\r\n */\r\nexport function isValidDetectedRoute(value?: string): value is string {\r\n return Boolean(value && value.startsWith('/') && !value.startsWith('//') && value !== '/');\r\n}\r\n\r\nexport async function detectCountry(request: NextRequest, defaultMarket?: string) {\r\n let ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip');\r\n if (ip) {\r\n ip = ip.split(',')[0]!.trim();\r\n }\r\n const isValidIp = ip?.match(/^(\\d+?)\\.(\\d+?)\\.(\\d+?)\\.(\\d+?)$/);\r\n const cachedCountry = isValidIp && ip ? getCachedCountry(ip) : undefined;\r\n if (cachedCountry !== undefined) {\r\n return cachedCountry || defaultMarket || process.env.DEFAULT_MARKET;\r\n }\r\n let detectedCountry = '';\r\n try {\r\n const token = process.env.IPINFO_TOKEN;\r\n if (token) {\r\n const url = isValidIp ?\r\n `https://api.ipinfo.io/lite/${ip}/country_code?token=${token}` :\r\n `https://api.ipinfo.io/lite/me/country_code?token=${token}`;\r\n const response = await fetch(url, { signal: AbortSignal.timeout(GEOIP_TIMEOUT) });\r\n if (response.ok) {\r\n const data = await response.text();\r\n detectedCountry = data.toLowerCase();\r\n }\r\n } else {\r\n const url = `https://geoip.websolute.it/Ip2Location/get_info.aspx?ipaddress=${isValidIp ? ip : ''}`;\r\n const response = await fetch(url, { signal: AbortSignal.timeout(GEOIP_TIMEOUT) });\r\n if (response.ok) {\r\n const xml = await response.text();\r\n detectedCountry = (\r\n ((xml.split('<CountryCode><![CDATA[')[1] || '').split(']]></CountryCode>')[0] || '')\r\n ).replace('-', '').toLowerCase();\r\n // console.log('detectedCountry', detectedCountry);\r\n }\r\n }\r\n // console.log('routeInterceptor.detectCountry', detectedCountry);\r\n if (isValidIp && ip) {\r\n setCachedCountry(ip, detectedCountry);\r\n }\r\n return detectedCountry || defaultMarket || process.env.DEFAULT_MARKET;\r\n } catch (error) {\r\n console.warn('routeInterceptor.detectCountry.error', error);\r\n }\r\n return (defaultMarket || process.env.DEFAULT_MARKET) as string;\r\n}\r\n\r\nexport const routeAutoDetection: Middleware = async ({ request }: MiddlewareOptions) => {\r\n let redirectUrl: string | undefined = undefined;\r\n const url = request.nextUrl;\r\n const origin = getRequestOrigin(request);\r\n if (url.pathname === '/') {\r\n const cookie = request.cookies.get('detectedRoute');\r\n if (isValidDetectedRoute(cookie?.value)) {\r\n // The cookie is only set after a successful detection, so trust it and\r\n // skip the route, market and GeoIP lookups entirely.\r\n return createDetectedRouteResponse(request, origin, cookie.value);\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection', url.pathname);\r\n const rootRoutes: IRoute[] = await localApiGet('/-route?where[isRoot][equals]=true', {}, undefined, origin);\r\n // console.log('routeInterceptor.routeAutoDetection.rootRoutes', rootRoutes);\r\n const defaultRoute = rootRoutes.find(x => x.id === '/');\r\n // console.log('routeInterceptor.routeAutoDetection.defaultRoute', defaultRoute);\r\n if (defaultRoute) {\r\n return;\r\n }\r\n const markets: IMarket[] = await localApiGet('/market?pagination=false', {}, undefined, origin);\r\n // console.log('routeInterceptor.routeAutoDetection.markets', markets);\r\n const market = markets.find(x => x.isDefault) || markets.find(x => x.id === defaultMarket) || markets[0];\r\n // console.log('routeInterceptor.routeAutoDetection.market', market);\r\n const detectedCountry = await detectCountry(request, market.id);\r\n // console.log('routeInterceptor.routeAutoDetection.detectedCountry', detectedCountry);\r\n const detectedMarket = detectedCountry ? (\r\n markets.filter(x => Array.isArray(x.countries)).find(x => {\r\n const countries: string[] = (x.countries || []).map(x => typeof x === 'string' ? x : x.id as string);\r\n return countries.includes(detectedCountry);\r\n }) ||\r\n markets.find(x => !Array.isArray(x.countries))\r\n ) : undefined;\r\n // console.log('routeInterceptor.routeAutoDetection.detectedMarket', detectedMarket);\r\n let detectedMarketRoutes = rootRoutes.filter(x => x.market === detectedMarket?.id);\r\n if (detectedMarketRoutes.length === 0) {\r\n detectedMarketRoutes = rootRoutes.filter(x => x.market === market.id);\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection.detectedMarketRoutes', detectedMarketRoutes);\r\n const defaultMarketLocale = market.defaultLanguage ? (\r\n typeof market.defaultLanguage === 'string' ? market.defaultLanguage : market.defaultLanguage.id\r\n ) as string : defaultLocale;\r\n // console.log('routeInterceptor.routeAutoDetection.defaultMarketLocale', defaultMarketLocale);\r\n const detectedLocale = await detectLocale(request, defaultMarketLocale);\r\n // console.log('routeInterceptor.routeAutoDetection.detectedLocale', detectedLocale);\r\n const detectedLocaleRoutes = detectedMarketRoutes.filter(x => x.locale === detectedLocale);\r\n const detectedRoute =\r\n detectedLocaleRoutes.length > 0 ?\r\n detectedLocaleRoutes[0]! :\r\n detectedMarketRoutes.find(x => {\r\n const market = markets.find(m => m.id === x.market)!;\r\n const marketLocale = (market.defaultLanguage ? asEquatable(market.defaultLanguage as string) : defaultMarketLocale);\r\n return x.locale === marketLocale;\r\n })!;\r\n // console.log('routeInterceptor.routeAutoDetection.detectedRoute', detectedRoute);\r\n if (detectedRoute && detectedRoute.id !== url.pathname) {\r\n debug('routeInterceptor.routeAutoDetection', detectedLocale, detectedCountry, detectedRoute.id);\r\n redirectUrl = detectedRoute.id;\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection', locale, detectedCountry, rootRoutes);\r\n }\r\n if (redirectUrl) {\r\n return createDetectedRouteResponse(request, origin, redirectUrl);\r\n }\r\n};\r\n\r\nfunction createDetectedRouteResponse(request: NextRequest, origin: string, redirectUrl: string) {\r\n const resolvedUrl = new URL(redirectUrl, origin).href;\r\n const search = request.nextUrl.search;\r\n const response = NextResponse.redirect(resolvedUrl + search, { status: 307 });\r\n response.cookies.set({\r\n expires: new Date(Date.now() + 1 * 24 * 60 * 60 * 1000),\r\n httpOnly: true,\r\n name: 'detectedRoute',\r\n path: '/',\r\n sameSite: 'lax',\r\n secure: true,\r\n value: redirectUrl,\r\n });\r\n return response;\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"route-auto-detection.js","names":["request: NextRequest","defaultLocale?: string","defaultLocale","ip: string","country: string","value?: string","decoded: string","defaultMarket?: string","defaultMarket","routeAutoDetection: Middleware","redirectUrl: string | undefined","rootRoutes: IRoute[]","markets: IMarket[]","countries: string[]","x","market","origin: string","redirectUrl: string"],"sources":["../../src/proxy/route-auto-detection.ts"],"sourcesContent":["import { asEquatable, defaultLocale, defaultMarket, IMarket, IRoute } from '@websolutespa/bom-core';\r\nimport { NextRequest, NextResponse } from 'next/server';\r\nimport { debug } from '../lib/debug';\r\nimport { localApiGet } from '../lib/http';\r\nimport { getRequestOrigin, Middleware, MiddlewareOptions } from './proxy';\r\n\r\nexport async function detectLocale(request: NextRequest, defaultLocale?: string) {\r\n const acceptLanguage = request.headers.get('accept-language')?.split(',')[0];\r\n const detectedLocale = request.cookies.get('NEXT_LOCALE')?.value || acceptLanguage || defaultLocale || process.env.DEFAULT_LOCALE;\r\n return detectedLocale as string;\r\n}\r\n\r\nconst GEOIP_TIMEOUT = 2000;\r\nconst GEOIP_CACHE_TTL = 60 * 60 * 1000;\r\nconst GEOIP_CACHE_MAX = 1000;\r\n\r\ntype GeoIpCacheEntry = { country: string; expires: number };\r\nconst globalForGeoIp = globalThis as typeof globalThis & { _bomGeoIpCache?: Map<string, GeoIpCacheEntry> };\r\n// Country lookups are external calls; cache them per IP so repeat visitors\r\n// and crawlers do not hit the GeoIP service on every request to `/`.\r\nconst geoIpCache = globalForGeoIp._bomGeoIpCache ??= new Map<string, GeoIpCacheEntry>();\r\n\r\nfunction getCachedCountry(ip: string): string | undefined {\r\n const entry = geoIpCache.get(ip);\r\n if (!entry) {\r\n return undefined;\r\n }\r\n if (entry.expires < Date.now()) {\r\n geoIpCache.delete(ip);\r\n return undefined;\r\n }\r\n return entry.country;\r\n}\r\n\r\nfunction setCachedCountry(ip: string, country: string) {\r\n if (geoIpCache.size >= GEOIP_CACHE_MAX) {\r\n // Maps iterate in insertion order, so the first key is the oldest entry.\r\n const oldest = geoIpCache.keys().next().value;\r\n if (oldest !== undefined) {\r\n geoIpCache.delete(oldest);\r\n }\r\n }\r\n geoIpCache.set(ip, { country, expires: Date.now() + GEOIP_CACHE_TTL });\r\n}\r\n\r\n/**\r\n * A relative, same-origin path previously set by this middleware. Anything\r\n * else (absolute or protocol-relative URLs) is ignored to avoid open redirects.\r\n */\r\nexport function isValidDetectedRoute(value?: string): value is string {\r\n if (!value || !value.startsWith('/') || value.startsWith('//') || value === '/' || value.includes('\\\\')) {\r\n return false;\r\n }\r\n // dot segments (also percent-encoded) would resolve to a different path on redirect\r\n let decoded: string;\r\n try {\r\n decoded = decodeURIComponent(value);\r\n } catch {\r\n return false;\r\n }\r\n return !decoded.split(/[/?#]/).some(segment => segment === '.' || segment === '..') && !decoded.includes('\\\\');\r\n}\r\n\r\nexport async function detectCountry(request: NextRequest, defaultMarket?: string) {\r\n let ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip');\r\n if (ip) {\r\n ip = ip.split(',')[0]!.trim();\r\n }\r\n const isValidIp = ip?.match(/^(\\d+?)\\.(\\d+?)\\.(\\d+?)\\.(\\d+?)$/);\r\n const cachedCountry = isValidIp && ip ? getCachedCountry(ip) : undefined;\r\n if (cachedCountry !== undefined) {\r\n return cachedCountry || defaultMarket || process.env.DEFAULT_MARKET;\r\n }\r\n let detectedCountry = '';\r\n try {\r\n const token = process.env.IPINFO_TOKEN;\r\n if (token) {\r\n const url = isValidIp ?\r\n `https://api.ipinfo.io/lite/${ip}/country_code?token=${token}` :\r\n `https://api.ipinfo.io/lite/me/country_code?token=${token}`;\r\n const response = await fetch(url, { signal: AbortSignal.timeout(GEOIP_TIMEOUT) });\r\n if (response.ok) {\r\n const data = await response.text();\r\n detectedCountry = data.toLowerCase();\r\n }\r\n } else {\r\n const url = `https://geoip.websolute.it/Ip2Location/get_info.aspx?ipaddress=${isValidIp ? ip : ''}`;\r\n const response = await fetch(url, { signal: AbortSignal.timeout(GEOIP_TIMEOUT) });\r\n if (response.ok) {\r\n const xml = await response.text();\r\n detectedCountry = (\r\n ((xml.split('<CountryCode><![CDATA[')[1] || '').split(']]></CountryCode>')[0] || '')\r\n ).replace('-', '').toLowerCase();\r\n // console.log('detectedCountry', detectedCountry);\r\n }\r\n }\r\n // console.log('routeInterceptor.detectCountry', detectedCountry);\r\n if (isValidIp && ip) {\r\n setCachedCountry(ip, detectedCountry);\r\n }\r\n return detectedCountry || defaultMarket || process.env.DEFAULT_MARKET;\r\n } catch (error) {\r\n console.warn('routeInterceptor.detectCountry.error', error);\r\n }\r\n return (defaultMarket || process.env.DEFAULT_MARKET) as string;\r\n}\r\n\r\nexport const routeAutoDetection: Middleware = async ({ request }: MiddlewareOptions) => {\r\n let redirectUrl: string | undefined = undefined;\r\n const url = request.nextUrl;\r\n const origin = getRequestOrigin(request);\r\n if (url.pathname === '/') {\r\n const cookie = request.cookies.get('detectedRoute');\r\n if (isValidDetectedRoute(cookie?.value)) {\r\n // The cookie is only set after a successful detection, so trust it and\r\n // skip the route, market and GeoIP lookups entirely.\r\n return createDetectedRouteResponse(request, origin, cookie.value);\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection', url.pathname);\r\n const rootRoutes: IRoute[] = await localApiGet('/-route?where[isRoot][equals]=true', {}, undefined, origin);\r\n // console.log('routeInterceptor.routeAutoDetection.rootRoutes', rootRoutes);\r\n const defaultRoute = rootRoutes.find(x => x.id === '/');\r\n // console.log('routeInterceptor.routeAutoDetection.defaultRoute', defaultRoute);\r\n if (defaultRoute) {\r\n return;\r\n }\r\n const markets: IMarket[] = await localApiGet('/market?pagination=false', {}, undefined, origin);\r\n // console.log('routeInterceptor.routeAutoDetection.markets', markets);\r\n const market = markets.find(x => x.isDefault) || markets.find(x => x.id === defaultMarket) || markets[0];\r\n // console.log('routeInterceptor.routeAutoDetection.market', market);\r\n const detectedCountry = await detectCountry(request, market.id);\r\n // console.log('routeInterceptor.routeAutoDetection.detectedCountry', detectedCountry);\r\n const detectedMarket = detectedCountry ? (\r\n markets.filter(x => Array.isArray(x.countries)).find(x => {\r\n const countries: string[] = (x.countries || []).map(x => typeof x === 'string' ? x : x.id as string);\r\n return countries.includes(detectedCountry);\r\n }) ||\r\n markets.find(x => !Array.isArray(x.countries))\r\n ) : undefined;\r\n // console.log('routeInterceptor.routeAutoDetection.detectedMarket', detectedMarket);\r\n let detectedMarketRoutes = rootRoutes.filter(x => x.market === detectedMarket?.id);\r\n if (detectedMarketRoutes.length === 0) {\r\n detectedMarketRoutes = rootRoutes.filter(x => x.market === market.id);\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection.detectedMarketRoutes', detectedMarketRoutes);\r\n const defaultMarketLocale = market.defaultLanguage ? (\r\n typeof market.defaultLanguage === 'string' ? market.defaultLanguage : market.defaultLanguage.id\r\n ) as string : defaultLocale;\r\n // console.log('routeInterceptor.routeAutoDetection.defaultMarketLocale', defaultMarketLocale);\r\n const detectedLocale = await detectLocale(request, defaultMarketLocale);\r\n // console.log('routeInterceptor.routeAutoDetection.detectedLocale', detectedLocale);\r\n const detectedLocaleRoutes = detectedMarketRoutes.filter(x => x.locale === detectedLocale);\r\n const detectedRoute =\r\n detectedLocaleRoutes.length > 0 ?\r\n detectedLocaleRoutes[0]! :\r\n detectedMarketRoutes.find(x => {\r\n const market = markets.find(m => m.id === x.market)!;\r\n const marketLocale = (market.defaultLanguage ? asEquatable(market.defaultLanguage as string) : defaultMarketLocale);\r\n return x.locale === marketLocale;\r\n })!;\r\n // console.log('routeInterceptor.routeAutoDetection.detectedRoute', detectedRoute);\r\n if (detectedRoute && detectedRoute.id !== url.pathname) {\r\n debug('routeInterceptor.routeAutoDetection', detectedLocale, detectedCountry, detectedRoute.id);\r\n redirectUrl = detectedRoute.id;\r\n }\r\n // console.log('routeInterceptor.routeAutoDetection', locale, detectedCountry, rootRoutes);\r\n }\r\n if (redirectUrl) {\r\n return createDetectedRouteResponse(request, origin, redirectUrl);\r\n }\r\n};\r\n\r\nfunction createDetectedRouteResponse(request: NextRequest, origin: string, redirectUrl: string) {\r\n const resolvedUrl = new URL(redirectUrl, origin).href;\r\n const search = request.nextUrl.search;\r\n const response = NextResponse.redirect(resolvedUrl + search, { status: 307 });\r\n response.cookies.set({\r\n expires: new Date(Date.now() + 1 * 24 * 60 * 60 * 1000),\r\n httpOnly: true,\r\n name: 'detectedRoute',\r\n path: '/',\r\n sameSite: 'lax',\r\n secure: true,\r\n value: redirectUrl,\r\n });\r\n return response;\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { IRoute } from "@websolutespa/bom-core";
2
+ import { NextRequest } from "next/server";
3
+ import { SanitizedConfig } from "payload";
4
+
5
+ //#region src/server/handlers/revalidate.handler.d.ts
6
+
7
+ /**
8
+ * Strategy for purging the CDN cache after routes have been revalidated.
9
+ * Implement this for your CDN provider (Cloudflare, Vercel, Fastly, ...).
10
+ *
11
+ * Receives every route object in a single call so providers that support
12
+ * batching can group cache invalidations.
13
+ *
14
+ * @param routes the list of route objects that were revalidated
15
+ */
16
+ type CdnPurgeHandler = (routes: IRoute[]) => Promise<void>;
17
+ /**
18
+ * Cloudflare CDN purge by Cache-Tag (route.page).
19
+ * Requires `CLOUDFLARE_ZONE_ID` and `CLOUDFLARE_API_TOKEN` env vars.
20
+ * No-op when the env vars are not configured.
21
+ *
22
+ * Purges distinct page tags in batches of up to 25 tags per request,
23
+ * invalidating all cached responses with those tags across alternate URLs/resources.
24
+ */
25
+ declare const cloudflarePurgeHandler: CdnPurgeHandler;
26
+ /**
27
+ * Resolves the CDN purge strategy from `CDN_STRATEGY` (e.g. `cloudflare`).
28
+ * Returns `undefined` when the strategy is unknown or not set, disabling the purge.
29
+ */
30
+ declare function resolveCdnPurgeHandler(strategy?: string | undefined): CdnPurgeHandler | undefined;
31
+ /**
32
+ * Creates the `/-revalidate` POST handler that revalidates (on-demand ISR) the
33
+ * Next.js app router pages of the routes matching the request body:
34
+ *
35
+ * - `{ "href": "/ww/en/products/xxxx" }` matches a single route by pathname
36
+ * - `{ "schema": "product", "page": "xxxx" }` matches every market/locale route of a document
37
+ *
38
+ * The request must carry `Authorization: Bearer ${MIXER_SECRET}`.
39
+ *
40
+ * Routes are read from the Bowl route store, which is invalidated by collection
41
+ * hooks, so a document that was just saved is already reflected.
42
+ * An optional CDN purge runs after the pages are revalidated; a purge failure is
43
+ * logged but never blocks the revalidation response.
44
+ */
45
+ declare function getRevalidateHandler(config: Promise<SanitizedConfig>, cdnPurge?: CdnPurgeHandler | undefined): (request: NextRequest) => Promise<Response>;
46
+ //# sourceMappingURL=revalidate.handler.d.ts.map
47
+ //#endregion
48
+ export { CdnPurgeHandler, cloudflarePurgeHandler, getRevalidateHandler, resolveCdnPurgeHandler };
49
+ //# sourceMappingURL=revalidate.handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.handler.d.ts","names":[],"sources":["../../../src/server/handlers/revalidate.handler.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAkBA;;;;AAA2D;AAU3D;AA6BA;AAgCgB,KAvEJ,eAAA,GAuEwB,CAAA,MAAA,EAvEG,MAuEH,EAAA,EAAA,GAvEgB,OAuEhB,CAAA,IAAA,CAAA;;;;;;;;AACS;cA9DhC,wBAAwB;;;;;iBA6BrB,sBAAA,iCAA6D;;;;;;;;;;;;;;;iBAgC7D,oBAAA,SAA6B,QAAQ,6BAA4B,wCAC/C,gBAAW,QAAA"}
@@ -0,0 +1,103 @@
1
+ import { resolveRoute } from "../../lib/route.js";
2
+ import { guest } from "../../lib/const.js";
3
+ import { getPayloadInstance, getRoutes } from "../mixer.js";
4
+ import { getHandlerErrorResponse } from "./error-response.js";
5
+ import { revalidatePath } from "next/cache";
6
+
7
+ //#region src/server/handlers/revalidate.handler.ts
8
+ /**
9
+ * Cloudflare CDN purge by Cache-Tag (route.page).
10
+ * Requires `CLOUDFLARE_ZONE_ID` and `CLOUDFLARE_API_TOKEN` env vars.
11
+ * No-op when the env vars are not configured.
12
+ *
13
+ * Purges distinct page tags in batches of up to 25 tags per request,
14
+ * invalidating all cached responses with those tags across alternate URLs/resources.
15
+ */
16
+ const cloudflarePurgeHandler = async (routes) => {
17
+ const zoneId = process.env.CLOUDFLARE_ZONE_ID;
18
+ const token = process.env.CLOUDFLARE_API_TOKEN;
19
+ if (!zoneId || !token) return;
20
+ const tags = [...new Set(routes.map((route) => String(route.page ?? "")).filter(Boolean))];
21
+ const chunkSize = 25;
22
+ for (let offset = 0; offset < tags.length; offset += chunkSize) {
23
+ const chunk = tags.slice(offset, offset + chunkSize);
24
+ const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`;
25
+ const response = await fetch(url, {
26
+ method: "POST",
27
+ headers: {
28
+ Authorization: `Bearer ${token}`,
29
+ "Content-Type": "application/json"
30
+ },
31
+ body: JSON.stringify({ tags: chunk })
32
+ });
33
+ const result = await response.json().catch(() => void 0);
34
+ if (!response.ok || result?.success === false) throw new Error(`cloudflarePurgeHandler.failed ${response.status} ${JSON.stringify(result)}`);
35
+ }
36
+ };
37
+ /**
38
+ * Resolves the CDN purge strategy from `CDN_STRATEGY` (e.g. `cloudflare`).
39
+ * Returns `undefined` when the strategy is unknown or not set, disabling the purge.
40
+ */
41
+ function resolveCdnPurgeHandler(strategy = process.env.CDN_STRATEGY) {
42
+ switch (strategy) {
43
+ case "cloudflare": return cloudflarePurgeHandler;
44
+ default: return void 0;
45
+ }
46
+ }
47
+ /**
48
+ * Creates the `/-revalidate` POST handler that revalidates (on-demand ISR) the
49
+ * Next.js app router pages of the routes matching the request body:
50
+ *
51
+ * - `{ "href": "/ww/en/products/xxxx" }` matches a single route by pathname
52
+ * - `{ "schema": "product", "page": "xxxx" }` matches every market/locale route of a document
53
+ *
54
+ * The request must carry `Authorization: Bearer ${MIXER_SECRET}`.
55
+ *
56
+ * Routes are read from the Bowl route store, which is invalidated by collection
57
+ * hooks, so a document that was just saved is already reflected.
58
+ * An optional CDN purge runs after the pages are revalidated; a purge failure is
59
+ * logged but never blocks the revalidation response.
60
+ */
61
+ function getRevalidateHandler(config, cdnPurge = resolveCdnPurgeHandler()) {
62
+ return async function(request) {
63
+ try {
64
+ const secret = process.env.MIXER_SECRET;
65
+ const bearer = request.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
66
+ if (!secret || bearer !== secret) return Response.json({ message: "Invalid token" }, { status: 401 });
67
+ const { href, schema, page } = await request.json().catch(() => ({})) || {};
68
+ let match;
69
+ if (href) match = (route) => route.id === href;
70
+ else if (schema && page !== void 0 && page !== "") match = (route) => route.schema === schema && String(route.page) === String(page);
71
+ else return Response.json({ message: "Missing href or schema and page" }, { status: 400 });
72
+ const payload = await getPayloadInstance({ config });
73
+ const routes = (await getRoutes({
74
+ payload,
75
+ user: guest
76
+ })).filter(match);
77
+ if (routes.length === 0) {
78
+ const target = href || `${schema}:${page}`;
79
+ return Response.json({ message: `Route not found ${target}` }, { status: 404 });
80
+ }
81
+ const paths = [...new Set(routes.map(resolveRoute))];
82
+ for (const path of paths) revalidatePath(path);
83
+ if (cdnPurge) try {
84
+ await cdnPurge(routes);
85
+ } catch (purgeError) {
86
+ console.error("revalidateHandler.cdnPurge", routes.map((route) => route.id), purgeError);
87
+ }
88
+ return Response.json({
89
+ revalidated: true,
90
+ paths,
91
+ now: Date.now()
92
+ });
93
+ } catch (error) {
94
+ const response = getHandlerErrorResponse(error);
95
+ console.error("revalidateHandler.error", response.status, error);
96
+ return response;
97
+ }
98
+ };
99
+ }
100
+
101
+ //#endregion
102
+ export { cloudflarePurgeHandler, getRevalidateHandler, resolveCdnPurgeHandler };
103
+ //# sourceMappingURL=revalidate.handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.handler.js","names":["cloudflarePurgeHandler: CdnPurgeHandler","routes: IRoute[]","config: Promise<SanitizedConfig>","cdnPurge: CdnPurgeHandler | undefined","request: NextRequest","match: (route: IRoute) => boolean","error: unknown"],"sources":["../../../src/server/handlers/revalidate.handler.ts"],"sourcesContent":["import type { IRoute } from '@websolutespa/bom-core';\nimport { revalidatePath } from 'next/cache';\nimport { NextRequest } from 'next/server';\nimport { SanitizedConfig } from 'payload';\nimport { guest } from '../../lib/const';\nimport { resolveRoute } from '../../lib/route';\nimport { getPayloadInstance, getRoutes } from '../mixer';\nimport { getHandlerErrorResponse } from './error-response';\n\n/**\n * Strategy for purging the CDN cache after routes have been revalidated.\n * Implement this for your CDN provider (Cloudflare, Vercel, Fastly, ...).\n *\n * Receives every route object in a single call so providers that support\n * batching can group cache invalidations.\n *\n * @param routes the list of route objects that were revalidated\n */\nexport type CdnPurgeHandler = (routes: IRoute[]) => Promise<void>;\n\n/**\n * Cloudflare CDN purge by Cache-Tag (route.page).\n * Requires `CLOUDFLARE_ZONE_ID` and `CLOUDFLARE_API_TOKEN` env vars.\n * No-op when the env vars are not configured.\n *\n * Purges distinct page tags in batches of up to 25 tags per request,\n * invalidating all cached responses with those tags across alternate URLs/resources.\n */\nexport const cloudflarePurgeHandler: CdnPurgeHandler = async (routes: IRoute[]) => {\n const zoneId = process.env.CLOUDFLARE_ZONE_ID;\n const token = process.env.CLOUDFLARE_API_TOKEN;\n if (!zoneId || !token) return;\n const tags = [...new Set(routes.map((route) => String(route.page ?? '')).filter(Boolean))];\n\n const chunkSize = 25;\n for (let offset = 0; offset < tags.length; offset += chunkSize) {\n const chunk = tags.slice(offset, offset + chunkSize);\n const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ tags: chunk }),\n });\n const result = await response.json().catch(() => undefined);\n if (!response.ok || result?.success === false) {\n throw new Error(`cloudflarePurgeHandler.failed ${response.status} ${JSON.stringify(result)}`);\n }\n }\n};\n\n/**\n * Resolves the CDN purge strategy from `CDN_STRATEGY` (e.g. `cloudflare`).\n * Returns `undefined` when the strategy is unknown or not set, disabling the purge.\n */\nexport function resolveCdnPurgeHandler(strategy = process.env.CDN_STRATEGY): CdnPurgeHandler | undefined {\n switch (strategy) {\n case 'cloudflare':\n return cloudflarePurgeHandler;\n default:\n return undefined;\n }\n}\n\ntype RevalidateBody = {\n /** route pathname (route.id), e.g. `/ww/en/products/xxxx` */\n href?: string;\n /** route schema, to be used together with `page` */\n schema?: string;\n /** route document id, to be used together with `schema` */\n page?: string | number;\n};\n\n/**\n * Creates the `/-revalidate` POST handler that revalidates (on-demand ISR) the\n * Next.js app router pages of the routes matching the request body:\n *\n * - `{ \"href\": \"/ww/en/products/xxxx\" }` matches a single route by pathname\n * - `{ \"schema\": \"product\", \"page\": \"xxxx\" }` matches every market/locale route of a document\n *\n * The request must carry `Authorization: Bearer ${MIXER_SECRET}`.\n *\n * Routes are read from the Bowl route store, which is invalidated by collection\n * hooks, so a document that was just saved is already reflected.\n * An optional CDN purge runs after the pages are revalidated; a purge failure is\n * logged but never blocks the revalidation response.\n */\nexport function getRevalidateHandler(config: Promise<SanitizedConfig>, cdnPurge: CdnPurgeHandler | undefined = resolveCdnPurgeHandler()) {\n return async function (request: NextRequest) {\n try {\n const secret = process.env.MIXER_SECRET;\n const bearer = request.headers.get('authorization')?.replace(/^Bearer\\s+/i, '');\n // An unset secret must never authorize a request without a token.\n if (!secret || bearer !== secret) {\n return Response.json({ message: 'Invalid token' }, { status: 401 });\n }\n\n const { href, schema, page }: RevalidateBody = await request.json().catch(() => ({})) || {};\n let match: (route: IRoute) => boolean;\n if (href) {\n match = route => route.id === href;\n } else if (schema && page !== undefined && page !== '') {\n match = route => route.schema === schema && String(route.page) === String(page);\n } else {\n return Response.json({ message: 'Missing href or schema and page' }, { status: 400 });\n }\n\n const payload = await getPayloadInstance({ config });\n const routes = (await getRoutes({ payload, user: guest })).filter(match);\n if (routes.length === 0) {\n const target = href || `${schema}:${page}`;\n return Response.json({ message: `Route not found ${target}` }, { status: 404 });\n }\n\n const paths = [...new Set(routes.map(resolveRoute))];\n for (const path of paths) {\n revalidatePath(path);\n }\n\n // CDN cache purge, batched into a single call (never blocks the response)\n if (cdnPurge) {\n try {\n await cdnPurge(routes);\n } catch (purgeError) {\n console.error('revalidateHandler.cdnPurge', routes.map(route => route.id), purgeError);\n }\n }\n return Response.json({ revalidated: true, paths, now: Date.now() });\n } catch (error: unknown) {\n // If there was an error, Next.js will continue\n // to show the last successfully generated page\n const response = getHandlerErrorResponse(error);\n console.error('revalidateHandler.error', response.status, error);\n return response;\n }\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { IPageContext, LayoutContextOptions, LayoutOption, LayoutOptions, PageContextOptions, PageSelect, SearchParams, TypedPageContext } from "../types.js";
1
+ import { IPageContext, LayoutContextOptions, LayoutOption, LayoutOptions, PageContextOptions, PageSelect, TypedPageContext } from "../types.js";
2
2
  import { CollectionSlug, SanitizedConfig } from "payload";
3
3
  import * as next0 from "next";
4
4
 
@@ -40,7 +40,6 @@ declare function createContextHelpers({
40
40
  locale: string;
41
41
  id: string;
42
42
  }>;
43
- searchParams?: Promise<SearchParams>;
44
43
  }>, _parent?: next0.ResolvingMetadata) => Promise<next0.Metadata>;
45
44
  };
46
45
  //#endregion