@websolutespa/bom-next 4.0.0-next.4 → 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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +89 -89
  3. package/dist/client/client.d.ts +2 -2
  4. package/dist/client/extra.d.ts.map +1 -1
  5. package/dist/client/label.d.ts +3 -3
  6. package/dist/client/label.d.ts.map +1 -1
  7. package/dist/client/layout.d.ts +3 -3
  8. package/dist/client/metadata.d.ts +3 -3
  9. package/dist/client/page.d.ts +3 -3
  10. package/dist/client/useApi.d.ts +7 -7
  11. package/dist/client/useApi.d.ts.map +1 -1
  12. package/dist/client/useCollectionApi.d.ts +6 -6
  13. package/dist/client/user.d.ts +3 -3
  14. package/dist/exports/server.d.ts +2 -1
  15. package/dist/exports/server.js +2 -1
  16. package/dist/lib/debug.js +3 -0
  17. package/dist/lib/debug.js.map +1 -1
  18. package/dist/lib/ttl-cache.js +5 -0
  19. package/dist/lib/ttl-cache.js.map +1 -1
  20. package/dist/server/handlers/revalidate.handler.d.ts +49 -0
  21. package/dist/server/handlers/revalidate.handler.d.ts.map +1 -0
  22. package/dist/server/handlers/revalidate.handler.js +103 -0
  23. package/dist/server/handlers/revalidate.handler.js.map +1 -0
  24. package/dist/server/json-ld.js +6 -0
  25. package/dist/server/json-ld.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/client/user.test.tsx +44 -44
  28. package/src/exports/server.ts +1 -0
  29. package/src/lib/debug.ts +11 -11
  30. package/src/lib/route.test.ts +53 -53
  31. package/src/lib/ttl-cache.ts +72 -72
  32. package/src/proxy/route-auto-detection.test.ts +55 -55
  33. package/src/server/context.type-test.ts +106 -106
  34. package/src/server/handlers/revalidate.handler.ts +139 -0
  35. package/src/server/handlers/route.handler.test.ts +39 -39
  36. package/src/server/json-ld.tsx +51 -51
  37. package/src/server/request-data.test.ts +37 -37
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 4.0.0-next.4
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -1,89 +1,89 @@
1
- # @websolutespa/bom-next
2
-
3
- [![npm version](https://badge.fury.io/js/%40websolutespa%2Fbom-next.svg)](https://badge.fury.io/js/%40websolutespa%2Fbom-next)
4
-
5
- [![status alpha](https://img.shields.io/badge/status-alpha-red.svg)](https://shields.io/)
6
-
7
- Next module of the [BOM Repository](https://github.com/websolutespa/bom) by [websolute](https://www.websolute.com/).
8
-
9
- ---
10
-
11
- ##### *this library is for internal usage and not production ready*
12
-
13
- ## Page context
14
-
15
- ```ts
16
- import { createPageContext, getMetadata } from '@websolutespa/bom-next/server';
17
-
18
- export const generateMetadata = getMetadata('post_detail', config);
19
-
20
- const { page, layout, label, link } = await createPageContext(market, locale, 'post_detail', id, {
21
- config,
22
- depth: 2, // default DEFAULT_PAGE_DEPTH
23
- select: { title: true, abstract: true, media: true, components: true },
24
- populate: { media: { url: true, alt: true, width: true, height: true, type: true } },
25
- });
26
-
27
- const { layout } = await createLayoutContext(market, locale, { config }); // depth: DEFAULT_LAYOUT_DEPTH (1)
28
- ```
29
-
30
- | option | default | |
31
- |---|---|---|
32
- | `depth` | `2` | `0` ids only, `1` direct relations/uploads (including those inside blocks), `2` relations of relations |
33
- | `select` | – | Payload select; envelope fields (`id`, `slug`, `title`, `category`, `markets`, `meta`, `template`) are always read |
34
- | `populate` | – | per-collection select for populated documents |
35
- | `draft` | `'auto'` | `true` / `false`, or `'auto'` to read Next `draftMode()` |
36
- | `layout` | `{ depth: 1 }` | layout options, or `false` to skip the layout |
37
-
38
- Page and layout reads are memoized per request, so `getMetadata` and the page component share them when called with the same options.
39
- To bind `config` once: `export const { createPageContext, createLayoutContext, getMetadata } = createContextHelpers({ config, defaults: { depth: 2 } });`
40
-
41
- ## Metadata
42
-
43
- `getPageMetadata` (and `getMetadata`, its `config`-first form) returns a `generateMetadata` function that renders on the server: plain-text title and description, canonical (keeping `?pagination[page]=N` after the first page), `alternates.languages` with one `locale-COUNTRY` entry per market country plus `x-default`, robots, keywords, Open Graph and Twitter.
44
-
45
- ```ts
46
- import { getPageMetadata, JsonLd, buildPageStructuredData } from '@websolutespa/bom-next/server';
47
-
48
- export const generateMetadata = getPageMetadata('post_detail', {
49
- config,
50
- metadata: {
51
- brand: { name: 'Example', logo: '/logo.png', twitterName: '@example' },
52
- extend: (metadata, { page }) => ({ ...metadata, title: `${metadata.title} | Example` }),
53
- },
54
- });
55
-
56
- // JSON-LD in the page component
57
- <JsonLd data={buildPageStructuredData(page, layout, { brand: { name: 'Example' } })} />
58
- ```
59
-
60
- `buildPageMetadata(page, layout, options)` is the same builder for custom routes. URLs are resolved against `NEXT_PUBLIC_URL` unless `baseUrl` is passed. Robots come from `meta.robots`, or `meta.robot` (the `@payloadcms/plugin-seo` field).
61
-
62
- The client `MetaDataProvider` only computes values for `useMetaData`; the `next/head` based `Meta`, `OpenGraph` and `StructuredData` components of the mixer packages render nothing in the App Router.
63
-
64
- `layout.homeHref` is the root page href for the current market and locale; prefer it to `layout.topLevelHrefs.<slug>`, which is keyed by collection slug or template.
65
-
66
- ## Routing proxy
67
-
68
- `createRouteProxy` resolves each request through `/api/-route` and caches the result per instance: found routes for `BOM_ROUTE_CACHE_TTL` seconds (default `30`, `0` disables), misses and redirects for at most 10 seconds. Draft requests and server errors are never cached. CMS redirects send the browser to the target returned by the resolver.
69
-
70
- `routeAutoDetection` redirects `/` straight from the `detectedRoute` cookie when present; otherwise it detects the market (GeoIP, 2 s timeout, cached per IP for one hour) and the locale.
71
-
72
- The server context (`getContext`) reads routes from the Bowl route store and caches the rest per market, locale and user for `MEMORY_CACHE_DURATION` seconds (default 300). Any route store change, local or from another instance through Redis, clears it. Draft contexts are never cached.
73
-
74
- ## Environment
75
-
76
- Per-request diagnostics (proxy, local API calls, context cache) are logged in development only (`NODE_ENV` unset or `development`).
77
-
78
- | Variable | Default | |
79
- |---|---|---|
80
- | `BOM_ROUTE_CACHE_TTL` | `30` | proxy route cache lifetime in seconds, `0` disables it |
81
- | `MEMORY_CACHE_DURATION` | `300` | server context cache lifetime in seconds |
82
- | `UI_REGISTRY_SOURCE_DIR` | `packages/ui/src` | registry source directory |
83
- | `UI_REGISTRY_COMPONENTS_JSON` | nearest `components.json` | shadcn config whose aliases (e.g. `@/ui/lib/utils`) are mapped to registry imports |
84
- | `UI_REGISTRY_SOURCE_ALIAS` | – | extra import prefix pointing at the registry source, e.g. `@/ui/` |
85
- | `UI_REGISTRY_SKIP_UNRESOLVED` | – | `1` hides registry items that import app code; otherwise they are listed with `meta.unresolvedImports` |
86
-
87
- ## User
88
-
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
+ # @websolutespa/bom-next
2
+
3
+ [![npm version](https://badge.fury.io/js/%40websolutespa%2Fbom-next.svg)](https://badge.fury.io/js/%40websolutespa%2Fbom-next)
4
+
5
+ [![status alpha](https://img.shields.io/badge/status-alpha-red.svg)](https://shields.io/)
6
+
7
+ Next module of the [BOM Repository](https://github.com/websolutespa/bom) by [websolute](https://www.websolute.com/).
8
+
9
+ ---
10
+
11
+ ##### *this library is for internal usage and not production ready*
12
+
13
+ ## Page context
14
+
15
+ ```ts
16
+ import { createPageContext, getMetadata } from '@websolutespa/bom-next/server';
17
+
18
+ export const generateMetadata = getMetadata('post_detail', config);
19
+
20
+ const { page, layout, label, link } = await createPageContext(market, locale, 'post_detail', id, {
21
+ config,
22
+ depth: 2, // default DEFAULT_PAGE_DEPTH
23
+ select: { title: true, abstract: true, media: true, components: true },
24
+ populate: { media: { url: true, alt: true, width: true, height: true, type: true } },
25
+ });
26
+
27
+ const { layout } = await createLayoutContext(market, locale, { config }); // depth: DEFAULT_LAYOUT_DEPTH (1)
28
+ ```
29
+
30
+ | option | default | |
31
+ |---|---|---|
32
+ | `depth` | `2` | `0` ids only, `1` direct relations/uploads (including those inside blocks), `2` relations of relations |
33
+ | `select` | – | Payload select; envelope fields (`id`, `slug`, `title`, `category`, `markets`, `meta`, `template`) are always read |
34
+ | `populate` | – | per-collection select for populated documents |
35
+ | `draft` | `'auto'` | `true` / `false`, or `'auto'` to read Next `draftMode()` |
36
+ | `layout` | `{ depth: 1 }` | layout options, or `false` to skip the layout |
37
+
38
+ Page and layout reads are memoized per request, so `getMetadata` and the page component share them when called with the same options.
39
+ To bind `config` once: `export const { createPageContext, createLayoutContext, getMetadata } = createContextHelpers({ config, defaults: { depth: 2 } });`
40
+
41
+ ## Metadata
42
+
43
+ `getPageMetadata` (and `getMetadata`, its `config`-first form) returns a `generateMetadata` function that renders on the server: plain-text title and description, canonical (keeping `?pagination[page]=N` after the first page), `alternates.languages` with one `locale-COUNTRY` entry per market country plus `x-default`, robots, keywords, Open Graph and Twitter.
44
+
45
+ ```ts
46
+ import { getPageMetadata, JsonLd, buildPageStructuredData } from '@websolutespa/bom-next/server';
47
+
48
+ export const generateMetadata = getPageMetadata('post_detail', {
49
+ config,
50
+ metadata: {
51
+ brand: { name: 'Example', logo: '/logo.png', twitterName: '@example' },
52
+ extend: (metadata, { page }) => ({ ...metadata, title: `${metadata.title} | Example` }),
53
+ },
54
+ });
55
+
56
+ // JSON-LD in the page component
57
+ <JsonLd data={buildPageStructuredData(page, layout, { brand: { name: 'Example' } })} />
58
+ ```
59
+
60
+ `buildPageMetadata(page, layout, options)` is the same builder for custom routes. URLs are resolved against `NEXT_PUBLIC_URL` unless `baseUrl` is passed. Robots come from `meta.robots`, or `meta.robot` (the `@payloadcms/plugin-seo` field).
61
+
62
+ The client `MetaDataProvider` only computes values for `useMetaData`; the `next/head` based `Meta`, `OpenGraph` and `StructuredData` components of the mixer packages render nothing in the App Router.
63
+
64
+ `layout.homeHref` is the root page href for the current market and locale; prefer it to `layout.topLevelHrefs.<slug>`, which is keyed by collection slug or template.
65
+
66
+ ## Routing proxy
67
+
68
+ `createRouteProxy` resolves each request through `/api/-route` and caches the result per instance: found routes for `BOM_ROUTE_CACHE_TTL` seconds (default `30`, `0` disables), misses and redirects for at most 10 seconds. Draft requests and server errors are never cached. CMS redirects send the browser to the target returned by the resolver.
69
+
70
+ `routeAutoDetection` redirects `/` straight from the `detectedRoute` cookie when present; otherwise it detects the market (GeoIP, 2 s timeout, cached per IP for one hour) and the locale.
71
+
72
+ The server context (`getContext`) reads routes from the Bowl route store and caches the rest per market, locale and user for `MEMORY_CACHE_DURATION` seconds (default 300). Any route store change, local or from another instance through Redis, clears it. Draft contexts are never cached.
73
+
74
+ ## Environment
75
+
76
+ Per-request diagnostics (proxy, local API calls, context cache) are logged in development only (`NODE_ENV` unset or `development`).
77
+
78
+ | Variable | Default | |
79
+ |---|---|---|
80
+ | `BOM_ROUTE_CACHE_TTL` | `30` | proxy route cache lifetime in seconds, `0` disables it |
81
+ | `MEMORY_CACHE_DURATION` | `300` | server context cache lifetime in seconds |
82
+ | `UI_REGISTRY_SOURCE_DIR` | `packages/ui/src` | registry source directory |
83
+ | `UI_REGISTRY_COMPONENTS_JSON` | nearest `components.json` | shadcn config whose aliases (e.g. `@/ui/lib/utils`) are mapped to registry imports |
84
+ | `UI_REGISTRY_SOURCE_ALIAS` | – | extra import prefix pointing at the registry source, e.g. `@/ui/` |
85
+ | `UI_REGISTRY_SKIP_UNRESOLVED` | – | `1` hides registry items that import app code; otherwise they are listed with `meta.unresolvedImports` |
86
+
87
+ ## User
88
+
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,7 +1,7 @@
1
- import * as react1 from "react";
1
+ import * as react0 from "react";
2
2
 
3
3
  //#region src/client/client.d.ts
4
- declare function Client(): react1.JSX.Element;
4
+ declare function Client(): react0.JSX.Element;
5
5
  //# sourceMappingURL=client.d.ts.map
6
6
 
7
7
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"extra.d.ts","names":[],"sources":["../../src/client/extra.tsx"],"sourcesContent":[],"mappings":";;;KAIY,aAAA,GAAgB;cAEf,cAAY,MAAA,CAAA,QAAA;iBAET,aAAA;;;;EAJJ,KAAA,CAAA,EAQF,MARE,CAAA,MAAa,EAAA,GAAA,CAAG;EAEf,QAAA,CAAA,EAOA,KAAA,CAAM,SAP4D;CAAA,CAAA,EAQ9E,MAAA,CAAA,GAAA,CAAA,OAR8E"}
1
+ {"version":3,"file":"extra.d.ts","names":[],"sources":["../../src/client/extra.tsx"],"sourcesContent":[],"mappings":";;;KAIY,aAAA,GAAgB;cAEf,cAAY,MAAA,CAAA,QAAA;iBAET,aAAA;;;;EAJJ,KAAA,CAAA,EAQF,MARE,CAAA,MAAa,EAAA,GAAA,CAAA;EAEZ,QAAA,CAAA,EAOA,KAAA,CAAM,SAP4D;CAAA,CAAA,EAQ9E,MAAA,CAAA,GAAA,CAAA,OAR8E"}
@@ -1,13 +1,13 @@
1
- import * as react2 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: react2.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
- }): react2.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 react4 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: react4.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
- }): react4.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 react6 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: react6.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
- }): react6.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 react8 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: react8.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
- }): react8.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 react15 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, react15.Dispatch<any>];
6
- declare function useApiGet<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react15.Dispatch<any>];
7
- declare function useApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react15.Dispatch<any>];
8
- declare function useApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react15.Dispatch<any>];
9
- declare function useApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react15.Dispatch<any>];
10
- declare function useApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react15.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 react10 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, react10.Dispatch<any>];
15
- declare function useCollectionApiPost<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react10.Dispatch<any>];
16
- declare function useCollectionApiPut<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react10.Dispatch<any>];
17
- declare function useCollectionApiPatch<T = unknown>(pathname: string, payload: unknown, options?: ApiRequestOptions): [T | undefined, boolean, any, react10.Dispatch<any>];
18
- declare function useCollectionApiDelete<T = unknown>(pathname: string, options?: ApiRequestOptions): [T | undefined, boolean, any, react10.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 react21 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: react21.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
- }): react21.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"}
@@ -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"}