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

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 (141) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +78 -28
  3. package/dist/client/client.d.ts +2 -2
  4. package/dist/client/extra.d.ts +3 -3
  5. package/dist/client/label.d.ts +3 -3
  6. package/dist/client/layout.d.ts +3 -3
  7. package/dist/client/layout.d.ts.map +1 -1
  8. package/dist/client/metadata.d.ts +13 -3
  9. package/dist/client/metadata.d.ts.map +1 -1
  10. package/dist/client/metadata.js +27 -46
  11. package/dist/client/metadata.js.map +1 -1
  12. package/dist/client/page.d.ts +4 -3
  13. package/dist/client/page.d.ts.map +1 -1
  14. package/dist/client/useApi.d.ts +7 -7
  15. package/dist/client/useCollectionApi.d.ts +6 -6
  16. package/dist/client/useCollectionApi.d.ts.map +1 -1
  17. package/dist/client/usePage.d.ts +1 -0
  18. package/dist/client/user.d.ts +15 -8
  19. package/dist/client/user.d.ts.map +1 -1
  20. package/dist/client/user.js +37 -14
  21. package/dist/client/user.js.map +1 -1
  22. package/dist/exports/client.d.ts +2 -2
  23. package/dist/exports/client.js +2 -2
  24. package/dist/exports/proxy.d.ts +3 -3
  25. package/dist/exports/proxy.js +3 -3
  26. package/dist/exports/server.d.ts +6 -4
  27. package/dist/exports/server.js +6 -4
  28. package/dist/lib/debug.js +14 -0
  29. package/dist/lib/debug.js.map +1 -0
  30. package/dist/lib/http.d.ts.map +1 -1
  31. package/dist/lib/http.js +2 -1
  32. package/dist/lib/http.js.map +1 -1
  33. package/dist/lib/meta-data.d.ts.map +1 -1
  34. package/dist/lib/page-metadata.d.ts +57 -0
  35. package/dist/lib/page-metadata.d.ts.map +1 -0
  36. package/dist/lib/page-metadata.js +205 -0
  37. package/dist/lib/page-metadata.js.map +1 -0
  38. package/dist/lib/ttl-cache.js +59 -0
  39. package/dist/lib/ttl-cache.js.map +1 -0
  40. package/dist/proxy/protected-route.d.ts.map +1 -1
  41. package/dist/proxy/protected-route.js +3 -2
  42. package/dist/proxy/protected-route.js.map +1 -1
  43. package/dist/proxy/route-auto-detection.d.ts +6 -1
  44. package/dist/proxy/route-auto-detection.d.ts.map +1 -1
  45. package/dist/proxy/route-auto-detection.js +61 -25
  46. package/dist/proxy/route-auto-detection.js.map +1 -1
  47. package/dist/proxy/route-proxy.d.ts +4 -1
  48. package/dist/proxy/route-proxy.d.ts.map +1 -1
  49. package/dist/proxy/route-proxy.js +96 -14
  50. package/dist/proxy/route-proxy.js.map +1 -1
  51. package/dist/server/handlers/registry.handler.d.ts.map +1 -1
  52. package/dist/server/handlers/registry.handler.js +77 -11
  53. package/dist/server/handlers/registry.handler.js.map +1 -1
  54. package/dist/server/handlers/route.handler.d.ts +12 -4
  55. package/dist/server/handlers/route.handler.d.ts.map +1 -1
  56. package/dist/server/handlers/route.handler.js +54 -39
  57. package/dist/server/handlers/route.handler.js.map +1 -1
  58. package/dist/server/helpers.d.ts +3 -5
  59. package/dist/server/helpers.d.ts.map +1 -1
  60. package/dist/server/json-ld.d.ts +33 -0
  61. package/dist/server/json-ld.d.ts.map +1 -0
  62. package/dist/server/json-ld.js +50 -0
  63. package/dist/server/json-ld.js.map +1 -0
  64. package/dist/server/layout.js +2 -1
  65. package/dist/server/layout.js.map +1 -1
  66. package/dist/server/lazy.js +8 -11
  67. package/dist/server/lazy.js.map +1 -1
  68. package/dist/server/metadata.d.ts +27 -11
  69. package/dist/server/metadata.d.ts.map +1 -1
  70. package/dist/server/metadata.js +29 -11
  71. package/dist/server/metadata.js.map +1 -1
  72. package/dist/server/mixer.d.ts +11 -4
  73. package/dist/server/mixer.d.ts.map +1 -1
  74. package/dist/server/mixer.js +93 -114
  75. package/dist/server/mixer.js.map +1 -1
  76. package/package.json +8 -5
  77. package/src/client/metadata.tsx +22 -57
  78. package/src/client/user.test.tsx +44 -0
  79. package/src/client/user.tsx +47 -14
  80. package/src/exports/server.ts +1 -0
  81. package/src/lib/debug.ts +11 -0
  82. package/src/lib/http.ts +2 -1
  83. package/src/lib/page-metadata.test.ts +126 -0
  84. package/src/lib/page-metadata.ts +231 -0
  85. package/src/lib/ttl-cache.ts +72 -0
  86. package/src/proxy/protected-route.ts +3 -2
  87. package/src/proxy/route-auto-detection.test.ts +50 -0
  88. package/src/proxy/route-auto-detection.ts +75 -24
  89. package/src/proxy/route-proxy.test.ts +93 -0
  90. package/src/proxy/route-proxy.ts +94 -18
  91. package/src/server/context.test.ts +64 -12
  92. package/src/server/handlers/registry.handler.test.ts +103 -11
  93. package/src/server/handlers/registry.handler.ts +98 -11
  94. package/src/server/handlers/route.handler.test.ts +39 -0
  95. package/src/server/handlers/route.handler.ts +61 -58
  96. package/src/server/json-ld.tsx +51 -0
  97. package/src/server/layout.ts +1 -0
  98. package/src/server/lazy.ts +6 -7
  99. package/src/server/metadata.ts +44 -78
  100. package/src/server/mixer.ts +101 -119
  101. package/dist/_virtual/rolldown_runtime.js +0 -27
  102. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js +0 -76
  103. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js.map +0 -1
  104. package/dist/node_modules/deepmerge/dist/cjs.js +0 -94
  105. package/dist/node_modules/deepmerge/dist/cjs.js.map +0 -1
  106. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js +0 -105
  107. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js.map +0 -1
  108. package/dist/node_modules/dom-serializer/lib/esm/index.js +0 -141
  109. package/dist/node_modules/dom-serializer/lib/esm/index.js.map +0 -1
  110. package/dist/node_modules/domelementtype/lib/esm/index.js +0 -53
  111. package/dist/node_modules/domelementtype/lib/esm/index.js.map +0 -1
  112. package/dist/node_modules/domhandler/lib/esm/index.js +0 -131
  113. package/dist/node_modules/domhandler/lib/esm/index.js.map +0 -1
  114. package/dist/node_modules/domhandler/lib/esm/node.js +0 -308
  115. package/dist/node_modules/domhandler/lib/esm/node.js.map +0 -1
  116. package/dist/node_modules/entities/lib/esm/decode.js +0 -364
  117. package/dist/node_modules/entities/lib/esm/decode.js.map +0 -1
  118. package/dist/node_modules/entities/lib/esm/decode_codepoint.js +0 -59
  119. package/dist/node_modules/entities/lib/esm/decode_codepoint.js.map +0 -1
  120. package/dist/node_modules/entities/lib/esm/escape.js +0 -93
  121. package/dist/node_modules/entities/lib/esm/escape.js.map +0 -1
  122. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js +0 -6
  123. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js.map +0 -1
  124. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js +0 -6
  125. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js.map +0 -1
  126. package/dist/node_modules/html-to-text/lib/html-to-text.js +0 -1828
  127. package/dist/node_modules/html-to-text/lib/html-to-text.js.map +0 -1
  128. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js +0 -435
  129. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js.map +0 -1
  130. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js +0 -739
  131. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js.map +0 -1
  132. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js +0 -20
  133. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js.map +0 -1
  134. package/dist/node_modules/leac/lib/leac.js +0 -101
  135. package/dist/node_modules/leac/lib/leac.js.map +0 -1
  136. package/dist/node_modules/parseley/lib/parseley.js +0 -314
  137. package/dist/node_modules/parseley/lib/parseley.js.map +0 -1
  138. package/dist/node_modules/peberminta/lib/core.js +0 -149
  139. package/dist/node_modules/peberminta/lib/core.js.map +0 -1
  140. package/dist/node_modules/selderee/lib/selderee.js +0 -325
  141. package/dist/node_modules/selderee/lib/selderee.js.map +0 -1
@@ -2,28 +2,51 @@
2
2
 
3
3
 
4
4
  import { localApiGet } from "../lib/http.js";
5
- import { createContext, useEffect, useState } from "react";
5
+ import { createContext, useCallback, useEffect, useState } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
 
8
8
  //#region src/client/user.tsx
9
9
  const UserContext = createContext({ setUser: () => {
10
10
  console.log("not implemented");
11
11
  } });
12
- function UserProvider({ children }) {
13
- const [user, setUser] = useState();
12
+ let mePromise;
13
+ function getMe() {
14
+ if (!mePromise) mePromise = localApiGet("/auth/me").catch((error) => {
15
+ console.warn("UserProvider.getMe.error", error);
16
+ mePromise = void 0;
17
+ return null;
18
+ });
19
+ return mePromise;
20
+ }
21
+ /** Forces the next UserProvider mount to read `/auth/me` again. */
22
+ function invalidateUser() {
23
+ mePromise = void 0;
24
+ }
25
+ /**
26
+
27
+ * Provides the current user. Pass `initialUser` (a user or `null`) when the
28
+
29
+ * server already knows it, to skip the `/auth/me` request entirely.
30
+
31
+ */
32
+ function UserProvider({ initialUser, children }) {
33
+ const [user, setUserState] = useState(initialUser);
14
34
  useEffect(() => {
15
- const getMe = async () => {
16
- try {
17
- const user$1 = await localApiGet("/auth/me");
18
- return user$1;
19
- } catch (error) {
20
- console.log("UserProvider.getMe.error", error);
21
- return null;
22
- }
23
- };
35
+ if (initialUser !== void 0) {
36
+ mePromise = Promise.resolve(initialUser);
37
+ return;
38
+ }
39
+ let active = true;
24
40
  getMe().then((user$1) => {
25
- setUser(user$1);
41
+ if (active) setUserState(user$1);
26
42
  });
43
+ return () => {
44
+ active = false;
45
+ };
46
+ }, [initialUser]);
47
+ const setUser = useCallback((user$1) => {
48
+ mePromise = user$1 === void 0 ? void 0 : Promise.resolve(user$1);
49
+ setUserState(user$1);
27
50
  }, []);
28
51
  return /* @__PURE__ */ jsx(UserContext.Provider, {
29
52
  value: {
@@ -35,5 +58,5 @@ function UserProvider({ children }) {
35
58
  }
36
59
 
37
60
  //#endregion
38
- export { UserContext, UserProvider };
61
+ export { UserContext, UserProvider, invalidateUser };
39
62
  //# sourceMappingURL=user.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"user.js","names":["user: IEndUser | null","user"],"sources":["../../src/client/user.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { createContext, ReactNode, useEffect, useState } from 'react';\r\nimport { localApiGet } from '../lib/http';\r\nimport { IEndUser, IUser } from '../types';\r\n\r\nexport type UserContextType = {\r\n user?: IEndUser | IUser | null;\r\n setUser: (user?: IEndUser | IUser | null) => void;\r\n};\r\n\r\nexport const UserContext = createContext<UserContextType>({\r\n setUser: () => {\r\n console.log('not implemented');\r\n },\r\n});\r\n\r\nexport function UserProvider({\r\n children,\r\n}: {\r\n children: ReactNode;\r\n}) {\r\n const [user, setUser] = useState<IEndUser | IUser | null | undefined>();\r\n useEffect(() => {\r\n const getMe = async () => {\r\n try {\r\n const user: IEndUser | null = await localApiGet('/auth/me');\r\n return user;\r\n } catch (error) {\r\n console.log('UserProvider.getMe.error', error);\r\n return null;\r\n }\r\n };\r\n getMe().then(user => {\r\n setUser(user);\r\n });\r\n }, []);\r\n return (\r\n <UserContext.Provider value={{ user, setUser }}>\r\n {children}\r\n </UserContext.Provider>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;AAWA,MAAa,cAAc,cAA+B,EACxD,SAAS,MAAM;CACb,QAAQ,IAAI,kBAAkB;AAC/B,EACF,EAAC;AAEF,SAAgB,aAAa,EAC3B,UAGD,EAAE;CACD,MAAM,CAAC,MAAM,QAAQ,GAAG,UAA+C;CACvE,UAAU,MAAM;EACd,MAAM,QAAQ,YAAY;AACxB,OAAI;IACF,MAAMA,SAAwB,MAAM,YAAY,WAAW;AAC3D,WAAOC;GACR,SAAQ,OAAO;IACd,QAAQ,IAAI,4BAA4B,MAAM;AAC9C,WAAO;GACR;EACF;EACD,OAAO,CAAC,KAAK,YAAQ;GACnB,QAAQA,OAAK;EACd,EAAC;CACH,GAAE,CAAE,EAAC;AACN,4BACG,YAAY;EAAS,OAAO;GAAE;GAAM;EAAS;EAC3C;GACoB;AAE1B"}
1
+ {"version":3,"file":"user.js","names":["mePromise: Promise<UserValue> | undefined","user","user?: UserValue"],"sources":["../../src/client/user.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { createContext, ReactNode, useCallback, useEffect, useState } from 'react';\r\nimport { localApiGet } from '../lib/http';\r\nimport { IEndUser, IUser } from '../types';\r\n\r\ntype UserValue = IEndUser | IUser | null;\r\n\r\nexport type UserContextType = {\r\n user?: UserValue;\r\n setUser: (user?: UserValue) => void;\r\n};\r\n\r\nexport const UserContext = createContext<UserContextType>({\r\n setUser: () => {\r\n console.log('not implemented');\r\n },\r\n});\r\n\r\n// One `/auth/me` request per browser session, shared by every UserProvider\r\n// mount; `setUser` and `invalidateUser` keep it current after login/logout.\r\nlet mePromise: Promise<UserValue> | undefined;\r\n\r\nfunction getMe(): Promise<UserValue> {\r\n if (!mePromise) {\r\n mePromise = localApiGet<UserValue>('/auth/me').catch(error => {\r\n console.warn('UserProvider.getMe.error', error);\r\n mePromise = undefined;\r\n return null;\r\n });\r\n }\r\n return mePromise;\r\n}\r\n\r\n/** Forces the next UserProvider mount to read `/auth/me` again. */\r\nexport function invalidateUser() {\r\n mePromise = undefined;\r\n}\r\n\r\n/**\r\n * Provides the current user. Pass `initialUser` (a user or `null`) when the\r\n * server already knows it, to skip the `/auth/me` request entirely.\r\n */\r\nexport function UserProvider({\r\n initialUser,\r\n children,\r\n}: {\r\n initialUser?: UserValue;\r\n children: ReactNode;\r\n}) {\r\n const [user, setUserState] = useState<UserValue | undefined>(initialUser);\r\n useEffect(() => {\r\n if (initialUser !== undefined) {\r\n mePromise = Promise.resolve(initialUser);\r\n return;\r\n }\r\n let active = true;\r\n getMe().then(user => {\r\n if (active) {\r\n setUserState(user);\r\n }\r\n });\r\n return () => {\r\n active = false;\r\n };\r\n }, [initialUser]);\r\n const setUser = useCallback((user?: UserValue) => {\r\n mePromise = user === undefined ? undefined : Promise.resolve(user);\r\n setUserState(user);\r\n }, []);\r\n return (\r\n <UserContext.Provider value={{ user, setUser }}>\r\n {children}\r\n </UserContext.Provider>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;AAaA,MAAa,cAAc,cAA+B,EACxD,SAAS,MAAM;CACb,QAAQ,IAAI,kBAAkB;AAC/B,EACF,EAAC;AAIF,IAAIA;AAEJ,SAAS,QAA4B;AACnC,KAAI,CAAC,WACH,YAAY,YAAuB,WAAW,CAAC,MAAM,WAAS;EAC5D,QAAQ,KAAK,4BAA4B,MAAM;EAC/C,YAAY;AACZ,SAAO;CACR,EAAC;AAEJ,QAAO;AACR;;AAGD,SAAgB,iBAAiB;CAC/B,YAAY;AACb;;;;;;;;AAMD,SAAgB,aAAa,EAC3B,aACA,UAID,EAAE;CACD,MAAM,CAAC,MAAM,aAAa,GAAG,SAAgC,YAAY;CACzE,UAAU,MAAM;AACd,MAAI,gBAAgB,QAAW;GAC7B,YAAY,QAAQ,QAAQ,YAAY;AACxC;EACD;EACD,IAAI,SAAS;EACb,OAAO,CAAC,KAAK,YAAQ;AACnB,OAAI,QACF,aAAaC,OAAK;EAErB,EAAC;AACF,SAAO,MAAM;GACX,SAAS;EACV;CACF,GAAE,CAAC,WAAY,EAAC;CACjB,MAAM,UAAU,YAAY,CAACC,WAAqB;EAChD,YAAYD,WAAS,SAAY,SAAY,QAAQ,QAAQA,OAAK;EAClE,aAAaA,OAAK;CACnB,GAAE,CAAE,EAAC;AACN,4BACG,YAAY;EAAS,OAAO;GAAE;GAAM;EAAS;EAC3C;GACoB;AAE1B"}
@@ -14,7 +14,7 @@ import { useLayout } from "../client/useLayout.js";
14
14
  import { useLink } from "../client/useLink.js";
15
15
  import { useMetaData } from "../client/useMetaData.js";
16
16
  import { usePage } from "../client/usePage.js";
17
- import { UserContext, UserContextType, UserProvider } from "../client/user.js";
17
+ import { UserContext, UserContextType, UserProvider, invalidateUser } from "../client/user.js";
18
18
  import { useUser } from "../client/useUser.js";
19
19
  import { captureRouterTransitionStart, registerClientIntrumentation } from "../instrumentation/client.js";
20
- export { Client, ExtraContext, ExtraProvider, IExtraContext, ILabelContext, ILayoutContext, IMetaDataContext, IMetaDataOptions, IPageContext, LabelContext, LabelProvider, LayoutContext, LayoutProvider, MetaDataContext, MetaDataProvider, PageContext, PageProvider, UserContext, UserContextType, UserProvider, captureRouterTransitionStart, localCollectionDelete, localCollectionFetch, localCollectionGet, localCollectionPatch, localCollectionPost, localCollectionPut, registerClientIntrumentation, useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut, useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useCollectionApiPatch, useCollectionApiPost, useCollectionApiPut, useExtra, useHttpDelete, useHttpFetch, useHttpGet, useHttpPatch, useHttpPost, useHttpPut, useLabel, useLayout, useLink, useMetaData, usePage, useUser };
20
+ export { Client, ExtraContext, ExtraProvider, IExtraContext, ILabelContext, ILayoutContext, IMetaDataContext, IMetaDataOptions, IPageContext, LabelContext, LabelProvider, LayoutContext, LayoutProvider, MetaDataContext, MetaDataProvider, PageContext, PageProvider, UserContext, UserContextType, UserProvider, captureRouterTransitionStart, invalidateUser, localCollectionDelete, localCollectionFetch, localCollectionGet, localCollectionPatch, localCollectionPost, localCollectionPut, registerClientIntrumentation, useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut, useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useCollectionApiPatch, useCollectionApiPost, useCollectionApiPut, useExtra, useHttpDelete, useHttpFetch, useHttpGet, useHttpPatch, useHttpPost, useHttpPut, useLabel, useLayout, useLink, useMetaData, usePage, useUser };
@@ -14,8 +14,8 @@ import { useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useColle
14
14
  import { useExtra } from "../client/useExtra.js";
15
15
  import { useLink } from "../client/useLink.js";
16
16
  import { useMetaData } from "../client/useMetaData.js";
17
- import { UserContext, UserProvider } from "../client/user.js";
17
+ import { UserContext, UserProvider, invalidateUser } from "../client/user.js";
18
18
  import { useUser } from "../client/useUser.js";
19
19
  import { captureRouterTransitionStart, registerClientIntrumentation } from "../instrumentation/client.js";
20
20
 
21
- export { Client, ExtraContext, ExtraProvider, LabelContext, LabelProvider, LayoutContext, LayoutProvider, MetaDataContext, MetaDataProvider, PageContext, PageProvider, UserContext, UserProvider, captureRouterTransitionStart, localCollectionDelete, localCollectionFetch, localCollectionGet, localCollectionPatch, localCollectionPost, localCollectionPut, registerClientIntrumentation, useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut, useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useCollectionApiPatch, useCollectionApiPost, useCollectionApiPut, useExtra, useHttpDelete, useHttpFetch, useHttpGet, useHttpPatch, useHttpPost, useHttpPut, useLabel, useLayout, useLink, useMetaData, usePage, useUser };
21
+ export { Client, ExtraContext, ExtraProvider, LabelContext, LabelProvider, LayoutContext, LayoutProvider, MetaDataContext, MetaDataProvider, PageContext, PageProvider, UserContext, UserProvider, captureRouterTransitionStart, invalidateUser, localCollectionDelete, localCollectionFetch, localCollectionGet, localCollectionPatch, localCollectionPost, localCollectionPut, registerClientIntrumentation, useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut, useCollectionApi, useCollectionApiDelete, useCollectionApiGet, useCollectionApiPatch, useCollectionApiPost, useCollectionApiPut, useExtra, useHttpDelete, useHttpFetch, useHttpGet, useHttpPatch, useHttpPost, useHttpPut, useLabel, useLayout, useLink, useMetaData, usePage, useUser };
@@ -1,5 +1,5 @@
1
1
  import { Middleware, MiddlewareOptions, createProxy, getRequestOrigin } from "../proxy/proxy.js";
2
- import { Proxy, ResolvedRoute, RouteInterceptorProxy, RouteMiddleware, RouteMiddlewareOptions, RouteResolverResponse, createRouteProxy } from "../proxy/route-proxy.js";
2
+ import { Proxy, ResolvedRoute, RouteInterceptorProxy, RouteMiddleware, RouteMiddlewareOptions, RouteResolverResponse, clearRouteProxyCache, createRouteProxy, getRouteResolverPath } from "../proxy/route-proxy.js";
3
3
  import { protectedRouteProxy } from "../proxy/protected-route.js";
4
- import { detectCountry, detectLocale, routeAutoDetection } from "../proxy/route-auto-detection.js";
5
- export { Middleware, MiddlewareOptions, Proxy, ResolvedRoute, RouteInterceptorProxy, RouteMiddleware, RouteMiddlewareOptions, RouteResolverResponse, createProxy, createRouteProxy, detectCountry, detectLocale, getRequestOrigin, protectedRouteProxy, routeAutoDetection };
4
+ import { detectCountry, detectLocale, isValidDetectedRoute, routeAutoDetection } from "../proxy/route-auto-detection.js";
5
+ export { Middleware, MiddlewareOptions, Proxy, ResolvedRoute, RouteInterceptorProxy, RouteMiddleware, RouteMiddlewareOptions, RouteResolverResponse, clearRouteProxyCache, createProxy, createRouteProxy, detectCountry, detectLocale, getRequestOrigin, getRouteResolverPath, isValidDetectedRoute, protectedRouteProxy, routeAutoDetection };
@@ -1,6 +1,6 @@
1
1
  import { createProxy, getRequestOrigin } from "../proxy/proxy.js";
2
2
  import { protectedRouteProxy } from "../proxy/protected-route.js";
3
- import { detectCountry, detectLocale, routeAutoDetection } from "../proxy/route-auto-detection.js";
4
- import { createRouteProxy } from "../proxy/route-proxy.js";
3
+ import { detectCountry, detectLocale, isValidDetectedRoute, routeAutoDetection } from "../proxy/route-auto-detection.js";
4
+ import { clearRouteProxyCache, createRouteProxy, getRouteResolverPath } from "../proxy/route-proxy.js";
5
5
 
6
- export { createProxy, createRouteProxy, detectCountry, detectLocale, getRequestOrigin, protectedRouteProxy, routeAutoDetection };
6
+ export { clearRouteProxyCache, createProxy, createRouteProxy, detectCountry, detectLocale, getRequestOrigin, getRouteResolverPath, isValidDetectedRoute, protectedRouteProxy, routeAutoDetection };
@@ -5,16 +5,18 @@ 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 { getRouteHandler } from "../server/handlers/route.handler.js";
8
+ import { getCompiledRedirects, getRouteHandler } from "../server/handlers/route.handler.js";
9
9
  import { ISiteMap, getSiteMapIndex, getSiteMapXML, getSiteMapXSL } from "../server/handlers/sitemap.handler.js";
10
10
  import { getStaticParams, getStaticParamsFromPayload, getStaticParams_ } from "../server/handlers/static-params.handler.js";
11
11
  import { getVarsHandler } from "../server/handlers/vars.handler.js";
12
12
  import { createContextHelpers } from "../server/helpers.js";
13
+ import { PageMetadataBrand, PageMetadataContext, PageMetadataOptions, PageMetadataSearchParams, buildPageMetadata, getPageAlternates, getPageXDefault, getPaginationQuery, inheritParentMetadata, resolveMetadataUrl, toPlainText } from "../lib/page-metadata.js";
14
+ import { JsonLd, PageStructuredDataOptions, buildPageStructuredData } from "../server/json-ld.js";
13
15
  import { decorateLazyComponents, withLazyProps } from "../server/lazy.js";
14
16
  import { getLocalApi } from "../server/local-api.js";
15
- import { getMetadata } from "../server/metadata.js";
16
- import { buildBaseOptions, buildReadOptions, filterRoutes, getAuthHeaders, getCacheKey, getContext, getMixerContext, getOptions, getPayload, getPayloadInstance, getRoutes, getSearchParams, resolveDraft, setContext } from "../server/mixer.js";
17
+ import { GetPageMetadataOptions, PageMetadataProps, getMetadata, getPageMetadata } from "../server/metadata.js";
18
+ import { buildBaseOptions, buildReadOptions, clearMixerContextCache, filterRoutes, getAuthHeaders, getCacheKey, getContext, getMixerContext, getOptions, getPayload, getPayloadInstance, getRoutes, getSearchParams, resolveDraft, setContext } from "../server/mixer.js";
17
19
  import { PAGE_ENVELOPE_FIELDS, getPage, withEnvelopeSelect } from "../server/page.js";
18
20
  import { getHrefBeforeRedirect, getNextUrl } from "../server/utils.js";
19
21
  import { getUtmz } from "../server/utmz.js";
20
- export { ISiteMap, PAGE_ENVELOPE_FIELDS, buildBaseOptions, buildReadOptions, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageContext, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, previewHandler, resolveDraft, setContext, setPageContext, stableStringify, withEnvelopeSelect, withLazyProps };
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 };
@@ -1,5 +1,6 @@
1
+ import { buildPageMetadata, getPageAlternates, getPageXDefault, getPaginationQuery, inheritParentMetadata, resolveMetadataUrl, toPlainText } from "../lib/page-metadata.js";
1
2
  import { getHrefBeforeRedirect, getNextUrl } from "../server/utils.js";
2
- import { buildBaseOptions, buildReadOptions, filterRoutes, getAuthHeaders, getCacheKey, getContext, getMixerContext, getOptions, getPayload, getPayloadInstance, getRoutes, getSearchParams, resolveDraft, setContext } from "../server/mixer.js";
3
+ import { buildBaseOptions, buildReadOptions, clearMixerContextCache, filterRoutes, getAuthHeaders, getCacheKey, getContext, getMixerContext, getOptions, getPayload, getPayloadInstance, getRoutes, getSearchParams, resolveDraft, setContext } from "../server/mixer.js";
3
4
  import { PAGE_ENVELOPE_FIELDS, getPage, withEnvelopeSelect } from "../server/page.js";
4
5
  import { createLayoutContext, createNotFoundContext, createPageContext, getPageContext, setPageContext, stableStringify } from "../server/context.js";
5
6
  import { getAuthMeHandler } from "../server/handlers/auth.handler.js";
@@ -9,13 +10,14 @@ import { collectionDeleteHandler, collectionGetHandler, collectionPatchHandler,
9
10
  import { indexGetHandler } from "../server/handlers/index.handler.js";
10
11
  import { previewHandler } from "../server/handlers/preview.handler.js";
11
12
  import { getRegistry, getRegistryItem } from "../server/handlers/registry.handler.js";
12
- import { getRouteHandler } from "../server/handlers/route.handler.js";
13
+ import { getCompiledRedirects, getRouteHandler } from "../server/handlers/route.handler.js";
13
14
  import { getSiteMapIndex, getSiteMapXML, getSiteMapXSL } from "../server/handlers/sitemap.handler.js";
14
15
  import { getStaticParams, getStaticParamsFromPayload, getStaticParams_ } from "../server/handlers/static-params.handler.js";
15
16
  import { getVarsHandler } from "../server/handlers/vars.handler.js";
16
- import { getMetadata } from "../server/metadata.js";
17
+ import { getMetadata, getPageMetadata } from "../server/metadata.js";
17
18
  import { createContextHelpers } from "../server/helpers.js";
19
+ import { JsonLd, buildPageStructuredData } from "../server/json-ld.js";
18
20
  import { decorateLazyComponents, withLazyProps } from "../server/lazy.js";
19
21
  import { getUtmz } from "../server/utmz.js";
20
22
 
21
- export { PAGE_ENVELOPE_FIELDS, buildBaseOptions, buildReadOptions, collectionDeleteHandler, collectionGetHandler, collectionPatchHandler, collectionPostHandler, createContextHelpers, createLayoutContext, createNotFoundContext, createPageContext, decorateLazyComponents, filterRoutes, getAuthHeaders, getAuthMeHandler, getCacheKey, getCaptionsVTT, getContext, getHrefBeforeRedirect, getLocalApi, getMetadata, getMixerContext, getNextUrl, getOptions, getPage, getPageContext, getPayload, getPayloadInstance, getRegistry, getRegistryItem, getRouteHandler, getRoutes, getSearchParams, getSiteMapIndex, getSiteMapXML, getSiteMapXSL, getStaticParams, getStaticParamsFromPayload, getStaticParams_, getUtmz, getVarsHandler, indexGetHandler, previewHandler, resolveDraft, setContext, setPageContext, stableStringify, withEnvelopeSelect, withLazyProps };
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 };
@@ -0,0 +1,14 @@
1
+ import { isDevelopment } from "@websolutespa/bom-core";
2
+
3
+ //#region src/lib/debug.ts
4
+ /**
5
+ * Per-request diagnostics (proxy, local API calls, context cache). They run
6
+ * on every page view and prefetch, so they are logged in development only.
7
+ */
8
+ function debug(...args) {
9
+ if (isDevelopment) console.log(...args);
10
+ }
11
+
12
+ //#endregion
13
+ export { debug };
14
+ //# sourceMappingURL=debug.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/lib/http.ts"],"sourcesContent":[],"mappings":";;;KAKY,iBAAA,GAAoB;;AAAhC;AAWA;AASA;AAqBA;AAMA;;;;AAA2K,iBApC3J,yBAAA,CAAA,CAoC2J,EApC9H,iBAoC8H;AAAO,iBA3BlK,0BAAA,CA2BkK,KAAA,CAAA,EAAA,MAAA,CAAA,EA3BtH,iBA2BsH;AAc5J,iBApBN,cAAA,CAoBiB,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAAA,iBAdX,aAcW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAd2C,iBAc3C,EAAA,cAA4I,CAA5I,EAAA;EAAA,MAAoC,EAAA,MAAA;EAAsB,MAAiF,EAAA,MAAA;CAAC,EAAA,MAAT,CAAA,EAAA,MAAA,CAAA,EAdO,OAcP,CAde,CAcf,CAAA;AAAO,iBAArJ,WAAqJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAtG,iBAAsG,EAAA,cAIzI,CAJyI,EAAA;EAIrJ,MAAA,EAAA,MAAA;EAAY,MAAA,EAAA,MAAA;CAAA,EAAA,MAAuD,CAAA,EAAA,MAAA,CAAA,EAJ2E,OAI3E,CAJmF,CAInF,CAAA;AAAsF,iBAAzJ,YAAyJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAAtF,iBAAsF,EAAA,cAI/K,CAJ+K,EAAA;EAAC,MAAT,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;AAI9K,CAAA,CAAA,EAJuK,OAIjJ,CAJyJ,CAIzJ,CAAA;AAAW,iBAAX,WAAW,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAAuD,iBAAvD,EAAA,cAA8I,CAA9I,EAAA;EAAA,MAAuD,EAAA,MAAA;EAAsB,MAAgE,EAAA,MAAA;CAAC,CAAA,EAAT,OAAA,CAAQ,CAAR,CAAA;AAAO,iBAIvJ,aAJuJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAInF,iBAJmF,EAAA,cAI1I,CAJ0I,EAAA;EAIvJ,MAAA,EAAA,MAAA;EAAa,MAAA,EAAA,MAAA;CAAA,CAAA,EAAqI,OAA9E,CAAsF,CAAtF,CAAA;AAAsF,iBAI1J,cAJ0J,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAIxG,iBAJwG,EAAA,cAIhL,CAJgL,EAAA;EAAC,MAAT,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;AAI/K,CAAA,CAAA,EAAsJ,OAAhI,CAAwI,CAAxI,CAAA"}
1
+ {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/lib/http.ts"],"sourcesContent":[],"mappings":";;;KAMY,iBAAA,GAAoB;;AAAhC;AAWA;AASA;AAqBA;AAMA;;;;AAA2K,iBApC3J,yBAAA,CAAA,CAoC2J,EApC9H,iBAoC8H;AAAO,iBA3BlK,0BAAA,CA2BkK,KAAA,CAAA,EAAA,MAAA,CAAA,EA3BtH,iBA2BsH;AAc5J,iBApBN,cAAA,CAoBiB,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAAA,iBAdX,aAcW,CAAA,IAAA,OAAA,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAd2C,iBAc3C,EAAA,cAA4I,CAA5I,EAAA;EAAA,MAAoC,EAAA,MAAA;EAAsB,MAAiF,EAAA,MAAA;CAAC,EAAA,MAAT,CAAA,EAAA,MAAA,CAAA,EAdO,OAcP,CAde,CAcf,CAAA;AAAO,iBAArJ,WAAqJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAtG,iBAAsG,EAAA,cAIzI,CAJyI,EAAA;EAIrJ,MAAA,EAAA,MAAA;EAAY,MAAA,EAAA,MAAA;CAAA,EAAA,MAAuD,CAAA,EAAA,MAAA,CAAA,EAJ2E,OAI3E,CAJmF,CAInF,CAAA;AAAsF,iBAAzJ,YAAyJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAAtF,iBAAsF,EAAA,cAI/K,CAJ+K,EAAA;EAAC,MAAT,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;AAI9K,CAAA,CAAA,EAJuK,OAIjJ,CAJyJ,CAIzJ,CAAA;AAAW,iBAAX,WAAW,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAAuD,iBAAvD,EAAA,cAA8I,CAA9I,EAAA;EAAA,MAAuD,EAAA,MAAA;EAAsB,MAAgE,EAAA,MAAA;CAAC,CAAA,EAAT,OAAA,CAAQ,CAAR,CAAA;AAAO,iBAIvJ,aAJuJ,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAInF,iBAJmF,EAAA,cAI1I,CAJ0I,EAAA;EAIvJ,MAAA,EAAA,MAAA;EAAa,MAAA,EAAA,MAAA;CAAA,CAAA,EAAqI,OAA9E,CAAsF,CAAtF,CAAA;AAAsF,iBAI1J,cAJ0J,CAAA,IAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAIxG,iBAJwG,EAAA,cAIhL,CAJgL,EAAA;EAAC,MAAT,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;AAI/K,CAAA,CAAA,EAAsJ,OAAhI,CAAwI,CAAxI,CAAA"}
package/dist/lib/http.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { debug } from "./debug.js";
1
2
  import { httpFetch, isBrowser, merge } from "@websolutespa/bom-core";
2
3
 
3
4
  //#region src/lib/http.ts
@@ -41,7 +42,7 @@ async function localApiFetch(pathname, options = {}, serviceOptions, origin) {
41
42
  url.searchParams.append("market", serviceOptions.market);
42
43
  }
43
44
  const apiOptions = merge({ ...getLocalApiRequestOptions() }, options);
44
- console.log("📡 localApiFetch", apiOptions.method, url.href);
45
+ debug("📡 localApiFetch", apiOptions.method, url.href);
45
46
  const apiResponse = await httpFetch(url.href, apiOptions);
46
47
  return apiResponse;
47
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","names":["headers: Record<string, string>","token?: string","pathname: string","origin?: string","options: ApiRequestOptions","serviceOptions?: { locale: string, market: string }","url: string","payload?: unknown"],"sources":["../../src/lib/http.ts"],"sourcesContent":["import { FetchRequestOptions, httpFetch, isBrowser, merge } from '@websolutespa/bom-core';\r\n\r\nconst API_URL = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';\r\nconst API_BASE = process.env.NEXT_PUBLIC_API || '/api';\r\n\r\nexport type ApiRequestOptions = FetchRequestOptions;\r\n\r\n/**\r\n * Default options for requests to this app's own API.\r\n *\r\n * Server-side calls (proxy, handlers) target the request's deployment URL,\r\n * which Vercel Deployment Protection answers with 401. When \"Protection Bypass\r\n * for Automation\" is enabled, Vercel exposes VERCEL_AUTOMATION_BYPASS_SECRET;\r\n * sending it as `x-vercel-protection-bypass` lets these calls through. The\r\n * secret is never sent from the browser.\r\n */\r\nexport function getLocalApiRequestOptions(): ApiRequestOptions {\r\n const headers: Record<string, string> = {};\r\n const bypassSecret = !isBrowser ? process.env.VERCEL_AUTOMATION_BYPASS_SECRET : undefined;\r\n if (bypassSecret) {\r\n headers['x-vercel-protection-bypass'] = bypassSecret;\r\n }\r\n return { headers };\r\n}\r\n\r\nexport function getRemoteApiRequestOptions(token?: string): ApiRequestOptions {\r\n if (token) {\r\n // authenticated end user JWT token bearer (end_users)\r\n return {\r\n // mode: 'cors',\r\n headers: {\r\n Authorization: `JWT ${token}`,\r\n // Authorization: `Bearer ${token}`,\r\n },\r\n };\r\n } else {\r\n // guest user api key (users)\r\n return {\r\n // mode: 'cors',\r\n headers: {\r\n Authorization: `users API-Key ${process.env.STORE_API_KEY}`,\r\n },\r\n };\r\n }\r\n}\r\n\r\nexport function getLocalApiUrl(pathname: string, origin?: string): string {\r\n const url = `${isBrowser ? window.location.origin : (origin || API_URL)}${API_BASE}${pathname}`;\r\n // console.log('getLocalApiUrl', url);\r\n return url;\r\n}\r\n\r\nexport async function localApiFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {\r\n // console.log('localApiFetch', pathname, options, serviceOptions);\r\n const url = new URL(getLocalApiUrl(pathname, origin));\r\n if (serviceOptions) {\r\n url.searchParams.append('locale', serviceOptions.locale);\r\n url.searchParams.append('market', serviceOptions.market);\r\n }\r\n const apiOptions = merge({ ...getLocalApiRequestOptions() }, options);\r\n console.log('📡 localApiFetch', apiOptions.method, url.href);\r\n const apiResponse = await httpFetch(url.href, apiOptions);\r\n // console.log('localApiFetch', pathname, apiResponse);\r\n return apiResponse;\r\n}\r\n\r\nexport async function localApiGet<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'GET' }, serviceOptions, origin);\r\n}\r\n\r\nexport async function localApiPost<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiPut<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiPatch<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiDelete<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'DELETE' }, serviceOptions);\r\n}\r\n"],"mappings":";;;AAEA,MAAM,UAAU,QAAQ,IAAI,mBAAmB;AAC/C,MAAM,WAAW,QAAQ,IAAI,mBAAmB;;;;;;;;;;;;;;;;;;AAahD,SAAgB,4BAA+C;CAC7D,MAAMA,UAAkC,CAAE;CAC1C,MAAM,eAAe,CAAC,YAAY,QAAQ,IAAI,kCAAkC;AAChF,KAAI,cACF,QAAQ,gCAAgC;AAE1C,QAAO,EAAE,QAAS;AACnB;AAED,SAAgB,2BAA2BC,OAAmC;AAC5E,KAAI,MAEF,QAAO,EAEL,SAAS,EACP,eAAe,CAAC,IAAI,EAAE,OAAO,CAE9B,EACF;KAGD,QAAO,EAEL,SAAS,EACP,eAAe,CAAC,cAAc,EAAE,QAAQ,IAAI,eAAe,CAC5D,EACF;AAEJ;AAED,SAAgB,eAAeC,UAAkBC,QAAyB;CACxE,MAAM,MAAM,GAAG,YAAY,OAAO,SAAS,SAAU,UAAU,UAAW,WAAW,UAAU;AAE/F,QAAO;AACR;AAED,eAAsB,cAA2BD,UAAkBE,UAA6B,CAAE,GAAEC,gBAAqDF,QAA6B;CAEpL,MAAM,MAAM,IAAI,IAAI,eAAe,UAAU,OAAO;AACpD,KAAI,gBAAgB;EAClB,IAAI,aAAa,OAAO,UAAU,eAAe,OAAO;EACxD,IAAI,aAAa,OAAO,UAAU,eAAe,OAAO;CACzD;CACD,MAAM,aAAa,MAAM,EAAE,GAAG,2BAA2B,CAAE,GAAE,QAAQ;CACrE,QAAQ,IAAI,oBAAoB,WAAW,QAAQ,IAAI,KAAK;CAC5D,MAAM,cAAc,MAAM,UAAU,IAAI,MAAM,WAAW;AAEzD,QAAO;AACR;AAED,eAAsB,YAAyBG,KAAaF,UAA6B,CAAE,GAAEC,gBAAqDF,QAA6B;AAC7K,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;CAAO,GAAE,gBAAgB,OAAO;AAC1F;AAED,eAAsB,aAA0BG,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AAChL,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAQ,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACxI;AAED,eAAsB,YAAyBC,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AAC/K,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAO,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACvI;AAED,eAAsB,cAA2BC,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AACjL,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAS,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACzI;AAED,eAAsB,eAA4BC,KAAaF,UAA6B,CAAE,GAAEC,gBAAiE;AAC/J,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;CAAU,GAAE,eAAe;AACrF"}
1
+ {"version":3,"file":"http.js","names":["headers: Record<string, string>","token?: string","pathname: string","origin?: string","options: ApiRequestOptions","serviceOptions?: { locale: string, market: string }","url: string","payload?: unknown"],"sources":["../../src/lib/http.ts"],"sourcesContent":["import { FetchRequestOptions, httpFetch, isBrowser, merge } from '@websolutespa/bom-core';\r\nimport { debug } from './debug';\r\n\r\nconst API_URL = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';\r\nconst API_BASE = process.env.NEXT_PUBLIC_API || '/api';\r\n\r\nexport type ApiRequestOptions = FetchRequestOptions;\r\n\r\n/**\r\n * Default options for requests to this app's own API.\r\n *\r\n * Server-side calls (proxy, handlers) target the request's deployment URL,\r\n * which Vercel Deployment Protection answers with 401. When \"Protection Bypass\r\n * for Automation\" is enabled, Vercel exposes VERCEL_AUTOMATION_BYPASS_SECRET;\r\n * sending it as `x-vercel-protection-bypass` lets these calls through. The\r\n * secret is never sent from the browser.\r\n */\r\nexport function getLocalApiRequestOptions(): ApiRequestOptions {\r\n const headers: Record<string, string> = {};\r\n const bypassSecret = !isBrowser ? process.env.VERCEL_AUTOMATION_BYPASS_SECRET : undefined;\r\n if (bypassSecret) {\r\n headers['x-vercel-protection-bypass'] = bypassSecret;\r\n }\r\n return { headers };\r\n}\r\n\r\nexport function getRemoteApiRequestOptions(token?: string): ApiRequestOptions {\r\n if (token) {\r\n // authenticated end user JWT token bearer (end_users)\r\n return {\r\n // mode: 'cors',\r\n headers: {\r\n Authorization: `JWT ${token}`,\r\n // Authorization: `Bearer ${token}`,\r\n },\r\n };\r\n } else {\r\n // guest user api key (users)\r\n return {\r\n // mode: 'cors',\r\n headers: {\r\n Authorization: `users API-Key ${process.env.STORE_API_KEY}`,\r\n },\r\n };\r\n }\r\n}\r\n\r\nexport function getLocalApiUrl(pathname: string, origin?: string): string {\r\n const url = `${isBrowser ? window.location.origin : (origin || API_URL)}${API_BASE}${pathname}`;\r\n // console.log('getLocalApiUrl', url);\r\n return url;\r\n}\r\n\r\nexport async function localApiFetch<T = unknown>(pathname: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {\r\n // console.log('localApiFetch', pathname, options, serviceOptions);\r\n const url = new URL(getLocalApiUrl(pathname, origin));\r\n if (serviceOptions) {\r\n url.searchParams.append('locale', serviceOptions.locale);\r\n url.searchParams.append('market', serviceOptions.market);\r\n }\r\n const apiOptions = merge({ ...getLocalApiRequestOptions() }, options);\r\n debug('📡 localApiFetch', apiOptions.method, url.href);\r\n const apiResponse = await httpFetch(url.href, apiOptions);\r\n // console.log('localApiFetch', pathname, apiResponse);\r\n return apiResponse;\r\n}\r\n\r\nexport async function localApiGet<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }, origin?: string): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'GET' }, serviceOptions, origin);\r\n}\r\n\r\nexport async function localApiPost<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiPut<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiPatch<T = unknown>(url: string, payload?: unknown, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined }, serviceOptions);\r\n}\r\n\r\nexport async function localApiDelete<T = unknown>(url: string, options: ApiRequestOptions = {}, serviceOptions?: { locale: string, market: string }): Promise<T> {\r\n return await localApiFetch<T>(url, { ...options, method: 'DELETE' }, serviceOptions);\r\n}\r\n"],"mappings":";;;;AAGA,MAAM,UAAU,QAAQ,IAAI,mBAAmB;AAC/C,MAAM,WAAW,QAAQ,IAAI,mBAAmB;;;;;;;;;;;;;;;;;;AAahD,SAAgB,4BAA+C;CAC7D,MAAMA,UAAkC,CAAE;CAC1C,MAAM,eAAe,CAAC,YAAY,QAAQ,IAAI,kCAAkC;AAChF,KAAI,cACF,QAAQ,gCAAgC;AAE1C,QAAO,EAAE,QAAS;AACnB;AAED,SAAgB,2BAA2BC,OAAmC;AAC5E,KAAI,MAEF,QAAO,EAEL,SAAS,EACP,eAAe,CAAC,IAAI,EAAE,OAAO,CAE9B,EACF;KAGD,QAAO,EAEL,SAAS,EACP,eAAe,CAAC,cAAc,EAAE,QAAQ,IAAI,eAAe,CAC5D,EACF;AAEJ;AAED,SAAgB,eAAeC,UAAkBC,QAAyB;CACxE,MAAM,MAAM,GAAG,YAAY,OAAO,SAAS,SAAU,UAAU,UAAW,WAAW,UAAU;AAE/F,QAAO;AACR;AAED,eAAsB,cAA2BD,UAAkBE,UAA6B,CAAE,GAAEC,gBAAqDF,QAA6B;CAEpL,MAAM,MAAM,IAAI,IAAI,eAAe,UAAU,OAAO;AACpD,KAAI,gBAAgB;EAClB,IAAI,aAAa,OAAO,UAAU,eAAe,OAAO;EACxD,IAAI,aAAa,OAAO,UAAU,eAAe,OAAO;CACzD;CACD,MAAM,aAAa,MAAM,EAAE,GAAG,2BAA2B,CAAE,GAAE,QAAQ;CACrE,MAAM,oBAAoB,WAAW,QAAQ,IAAI,KAAK;CACtD,MAAM,cAAc,MAAM,UAAU,IAAI,MAAM,WAAW;AAEzD,QAAO;AACR;AAED,eAAsB,YAAyBG,KAAaF,UAA6B,CAAE,GAAEC,gBAAqDF,QAA6B;AAC7K,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;CAAO,GAAE,gBAAgB,OAAO;AAC1F;AAED,eAAsB,aAA0BG,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AAChL,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAQ,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACxI;AAED,eAAsB,YAAyBC,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AAC/K,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAO,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACvI;AAED,eAAsB,cAA2BC,KAAaC,SAAmBH,UAA6B,CAAE,GAAEC,gBAAiE;AACjL,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;EAAS,MAAM,UAAU,KAAK,UAAU,QAAQ,GAAG;CAAW,GAAE,eAAe;AACzI;AAED,eAAsB,eAA4BC,KAAaF,UAA6B,CAAE,GAAEC,gBAAiE;AAC/J,QAAO,MAAM,cAAiB,KAAK;EAAE,GAAG;EAAS,QAAQ;CAAU,GAAE,eAAe;AACrF"}
@@ -1 +1 @@
1
- {"version":3,"file":"meta-data.d.ts","names":[],"sources":["../../src/lib/meta-data.ts"],"sourcesContent":[],"mappings":";;;;;;;KAMY,MAAA;;EAAA,YAAM,EAAA,MAAA;EAmBN,OAAA,EAAA,MAAU;EAMV,IAAA,EAAA,MAAK;EAAA,GAAA,EAAA,MAAA;EAAA,KAOH,CAAA,EAAA,MAAA;EAAU,SACX,CAAA,EAAA,MAAA;EAAU,OAAA,CAAA,EAAA,MAAA;EASX,IAAA,CAAA,EAAA,MAAS;EAAA,OAAA,CAAA,EAAA,MAAA;EAAA,YACX,CAAA,EAAA,MAAA;EAAO,WACE,CAAA,EAAA,MAAA;EAAK,WAAhB,CAAA,EAAA,MAAA;EAAU,MACT,CAAA,EA7BE,MA6BF,CAAA,MAAA,EAAA,MAAA,CAAA;CAAM;AAEP,KA5BI,UAAA,GA4BJ;EAAK,EAAA,EACA,MAAA;EAAa,IACV,EAAA,MAAA;EAAY,QACX,EAAA,MAAA;CAAa;AAEd,KA3BJ,KAAA,GA2BI;EAAe,KAAA,EAAA,MAAA;;;;;;cApBjB;aACD;;;;KASD,SAAA;UACF;QACF,WAAW;SACV;gBACO;QACR;aACK;gBACG;iBACC;kBACC;gBACF"}
1
+ {"version":3,"file":"meta-data.d.ts","names":[],"sources":["../../src/lib/meta-data.ts"],"sourcesContent":[],"mappings":";;;;;;;KAMY,MAAA;;EAAA,YAAM,EAAA,MAgBP;EAGC,OAAA,EAAA,MAAU;EAMV,IAAA,EAAA,MAAK;EAAA,GAAA,EAAA,MAAA;EAAA,KAOH,CAAA,EAAA,MAAA;EAAU,SACX,CAAA,EAAA,MAAA;EAAU,OAAA,CAAA,EAAA,MAAA;EASX,IAAA,CAAA,EAAA,MAAS;EAAA,OAAA,CAAA,EAAA,MAAA;EAAA,YACX,CAAA,EAAA,MAAA;EAAO,WACE,CAAA,EAAA,MAAA;EAAK,WAAhB,CAAA,EAAA,MAAA;EAAU,MACT,CAAA,EA7BE,MA6BF,CAAA,MAAA,EAAA,MAAA,CAAA;CAAM;AAEP,KA5BI,UAAA,GA4BJ;EAAK,EAAA,EACA,MAAA;EAAa,IACV,EAAA,MAAA;EAAY,QACX,EAAA,MAAA;CAAa;AAEd,KA3BJ,KAAA,GA2BI;EAAe,KAAA,EAAA,MAAA;;;;;;cApBjB;aACD;;;;KASD,SAAA;UACF;QACF,WAAW;SACV;gBACO;QACR;aACK;gBACG;iBACC;kBACC;gBACF"}
@@ -0,0 +1,57 @@
1
+ import { IAlternate } from "./meta-data.js";
2
+ import { ILayout, IPage } from "@websolutespa/bom-core";
3
+ import { Metadata, ResolvedMetadata } from "next";
4
+
5
+ //#region src/lib/page-metadata.d.ts
6
+ type PageMetadataBrand = {
7
+ /** Site name, used for Open Graph `siteName`. */
8
+ name: string;
9
+ /** Fallback image when the page has no meta image or media. */
10
+ logo?: string;
11
+ /** Twitter handle, used for `twitter:site` and `twitter:creator`. */
12
+ twitterName?: string;
13
+ };
14
+ type PageMetadataSearchParams = URLSearchParams | Record<string, string | string[] | undefined>;
15
+ type PageMetadataContext = {
16
+ page: IPage;
17
+ layout?: ILayout;
18
+ };
19
+ type PageMetadataOptions = {
20
+ brand?: PageMetadataBrand;
21
+ /** Absolute origin for canonical, alternates and images. Default `NEXT_PUBLIC_URL`. */
22
+ baseUrl?: string;
23
+ /** Request search params; `pagination[page]` > 1 is kept in the canonical URL. */
24
+ searchParams?: PageMetadataSearchParams;
25
+ /** Per-page override applied to the computed metadata. */
26
+ extend?: (metadata: Metadata, context: PageMetadataContext) => Metadata;
27
+ };
28
+ /** Converts CMS HTML (titles, abstracts) to a single line of plain text. */
29
+ declare function toPlainText(html?: string | null): string;
30
+ /** Resolves a relative href against `baseUrl`; absolute URLs are returned as is. */
31
+ declare function resolveMetadataUrl(href: string | undefined, baseUrl: string): string | undefined;
32
+ /** Returns `?pagination[page]=N` for pages after the first, otherwise an empty string. */
33
+ declare function getPaginationQuery(searchParams?: PageMetadataSearchParams): string;
34
+ /**
35
+ * Builds hreflang alternates from the page's routes. Markets with countries
36
+ * produce one `locale-COUNTRY` entry per country; others use the locale only.
37
+ */
38
+ declare function getPageAlternates(page: Pick<IPage, 'alternates'>, layout: Pick<ILayout, 'markets'>, baseUrl: string): IAlternate[];
39
+ /** The alternate for the default market and locale, used as `x-default`. */
40
+ declare function getPageXDefault(page: Pick<IPage, 'alternates'>, layout: Pick<ILayout, 'markets' | 'locales'>, baseUrl: string): IAlternate | undefined;
41
+ /**
42
+ * Builds the Next.js `Metadata` for a page: plain-text title and description,
43
+ * canonical (keeping pagination), hreflang alternates with `x-default`,
44
+ * robots, keywords, Open Graph and Twitter card.
45
+ */
46
+ declare function buildPageMetadata(page: IPage, layout?: ILayout, options?: PageMetadataOptions): Metadata;
47
+ /**
48
+ * Next.js replaces nested `openGraph` and `twitter` objects instead of merging
49
+ * them, so a page that sets them would drop the layout's images, site name or
50
+ * handles. Fills the fields the page left empty from the parent metadata.
51
+ */
52
+ declare function inheritParentMetadata(metadata: Metadata, parent?: ResolvedMetadata): Metadata;
53
+ //# sourceMappingURL=page-metadata.d.ts.map
54
+
55
+ //#endregion
56
+ export { PageMetadataBrand, PageMetadataContext, PageMetadataOptions, PageMetadataSearchParams, buildPageMetadata, getPageAlternates, getPageXDefault, getPaginationQuery, inheritParentMetadata, resolveMetadataUrl, toPlainText };
57
+ //# sourceMappingURL=page-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-metadata.d.ts","names":[],"sources":["../../src/lib/page-metadata.ts"],"sourcesContent":[],"mappings":";;;;;KAMY,iBAAA;;EAAA,IAAA,EAAA,MAAA;EASA;EAAwB,IAAA,CAAA,EAAA,MAAA;EAAA;EAAkB,WAAG,CAAA,EAAA,MAAA;AAAM,CAAA;AAEnD,KAFA,wBAAA,GAA2B,eAER,GAF0B,MAE1B,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,EAAA,GAAA,SAAA,CAAA;AAAA,KAAnB,mBAAA,GAAmB;EAAA,IACvB,EAAA,KAAA;EAAK,MACF,CAAA,EAAA,OAAA;AAAO,CAAA;AAGN,KAAA,mBAAA,GAAmB;EAAA,KAAA,CAAA,EACrB,iBADqB;EAAA;EACJ,OAIV,CAAA,EAAA,MAAA;EAAwB;EAEX,YAAW,CAAA,EAFxB,wBAEwB;EAAmB;EAAa,MAAA,CAAA,EAAA,CAAA,QAAA,EAAnD,QAAmD,EAAA,OAAA,EAAhC,mBAAgC,EAAA,GAAR,QAAQ;AAazE,CAAA;AAkBA;AAmBgB,iBArCA,WAAA,CAqCkB,IAAgB,CAAA,EAAA,MAAA,GAAA,IAAA,CAAA,EAAA,MAAwB;AAU1E;AAAiC,iBA7BjB,kBAAA,CA6BiB,IAAA,EAAA,MAAA,GAAA,SAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA;;AAAO,iBAVxB,kBAAA,CAUwB,YAAA,CAAA,EAVU,wBAUV,CAAA,EAAA,MAAA;;;;AAAyF;AAgCjH,iBAhCA,iBAAA,CAgCe,IAAA,EAhCS,IAgCT,CAhCc,KAgCd,EAAA,YAAA,CAAA,EAAA,MAAA,EAhC4C,IAgC5C,CAhCiD,OAgCjD,EAAA,SAAA,CAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAhCwF,UAgCxF,EAAA;;AAAY,iBAA3B,eAAA,CAA2B,IAAA,EAAL,IAAK,CAAA,KAAA,EAAA,YAAA,CAAA,EAAA,MAAA,EAA8B,IAA9B,CAAmC,OAAnC,EAAA,SAAA,GAAA,SAAA,CAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAsF,UAAtF,GAAA,SAAA;;;;;AAAgG;AA2B3H,iBAAA,iBAAA,CAAiB,IAAA,EAAO,KAAP,EAAA,MAAA,CAAA,EAAuB,OAAvB,EAAA,OAAA,CAAA,EAAyC,mBAAzC,CAAA,EAAoE,QAApE;;;;;;AAA4E,iBAwD7F,qBAAA,CAxD6F,QAAA,EAwD7D,QAxD6D,EAAA,MAAA,CAAA,EAwD1C,gBAxD0C,CAAA,EAwDvB,QAxDuB;AAwD7G"}
@@ -0,0 +1,205 @@
1
+ import { getPublicUrl } from "./utils.js";
2
+ import { defaultLocale, defaultMarket, getMetaRobots } from "@websolutespa/bom-core";
3
+ import { convert } from "html-to-text";
4
+
5
+ //#region src/lib/page-metadata.ts
6
+ /** Converts CMS HTML (titles, abstracts) to a single line of plain text. */
7
+ function toPlainText(html) {
8
+ if (!html) return "";
9
+ return convert(html, {
10
+ wordwrap: false,
11
+ selectors: [
12
+ {
13
+ selector: "a",
14
+ options: { ignoreHref: true }
15
+ },
16
+ {
17
+ selector: "img",
18
+ format: "skip"
19
+ },
20
+ ...[
21
+ "h1",
22
+ "h2",
23
+ "h3",
24
+ "h4",
25
+ "h5",
26
+ "h6"
27
+ ].map((selector) => ({
28
+ selector,
29
+ options: { uppercase: false }
30
+ })),
31
+ {
32
+ selector: "ul",
33
+ options: { itemPrefix: " " }
34
+ },
35
+ {
36
+ selector: "table",
37
+ format: "dataTable",
38
+ options: { uppercaseHeaderCells: false }
39
+ }
40
+ ]
41
+ }).replace(/\s+/g, " ").trim();
42
+ }
43
+ /** Resolves a relative href against `baseUrl`; absolute URLs are returned as is. */
44
+ function resolveMetadataUrl(href, baseUrl) {
45
+ if (!href) return void 0;
46
+ return /^https?:\/\//.test(href) ? href : `${baseUrl}${href}`;
47
+ }
48
+ function getSearchParam(searchParams, key) {
49
+ if (!searchParams) return void 0;
50
+ if (searchParams instanceof URLSearchParams) return searchParams.get(key) ?? void 0;
51
+ const value = searchParams[key];
52
+ return Array.isArray(value) ? value[0] : value;
53
+ }
54
+ /** Returns `?pagination[page]=N` for pages after the first, otherwise an empty string. */
55
+ function getPaginationQuery(searchParams) {
56
+ const value = getSearchParam(searchParams, "pagination[page]");
57
+ const page = value ? parseInt(value) : NaN;
58
+ return Number.isInteger(page) && page > 1 ? `?pagination[page]=${page}` : "";
59
+ }
60
+ /**
61
+
62
+ * Builds hreflang alternates from the page's routes. Markets with countries
63
+
64
+ * produce one `locale-COUNTRY` entry per country; others use the locale only.
65
+
66
+ */
67
+ function getPageAlternates(page, layout, baseUrl) {
68
+ const alternates = [];
69
+ (page.alternates || []).forEach((alternate) => {
70
+ const market = layout.markets.find((x) => x.id === alternate.market);
71
+ if (!market) return;
72
+ const href = resolveMetadataUrl(alternate.id, baseUrl);
73
+ const countries = market.countries || [];
74
+ if (countries.length > 0) countries.forEach((country) => {
75
+ const code = typeof country === "string" ? country : country.alpha2Code || country.id;
76
+ if (code) alternates.push({
77
+ id: `${alternate.market}-${alternate.locale}-${code}`,
78
+ href,
79
+ hrefLang: `${alternate.locale}-${code.toUpperCase()}`
80
+ });
81
+ });
82
+ else alternates.push({
83
+ id: `${alternate.market}-${alternate.locale}`,
84
+ href,
85
+ hrefLang: alternate.locale
86
+ });
87
+ });
88
+ return alternates;
89
+ }
90
+ /** The alternate for the default market and locale, used as `x-default`. */
91
+ function getPageXDefault(page, layout, baseUrl) {
92
+ const market = layout.markets.find((x) => x.isDefault)?.id || defaultMarket;
93
+ const locale = layout.locales.find((x) => x.isDefault)?.id || defaultLocale;
94
+ const alternate = (page.alternates || []).find((x) => x.market === market && x.locale === locale);
95
+ if (!alternate) return void 0;
96
+ return {
97
+ id: `${alternate.market}-${alternate.locale}`,
98
+ href: resolveMetadataUrl(alternate.id, baseUrl),
99
+ hrefLang: alternate.locale
100
+ };
101
+ }
102
+ function getMediaUrl(media) {
103
+ if (media && typeof media === "object") {
104
+ const { src, url } = media;
105
+ return src || url;
106
+ }
107
+ return void 0;
108
+ }
109
+ /**
110
+
111
+ * Builds the Next.js `Metadata` for a page: plain-text title and description,
112
+
113
+ * canonical (keeping pagination), hreflang alternates with `x-default`,
114
+
115
+ * robots, keywords, Open Graph and Twitter card.
116
+
117
+ */
118
+ function buildPageMetadata(page, layout, options = {}) {
119
+ const baseUrl = (options.baseUrl ?? getPublicUrl()).replace(/\/$/, "");
120
+ const meta = page.meta || {};
121
+ const title = toPlainText(meta.title || page.title);
122
+ const description = toPlainText(meta.description || page.abstract) || void 0;
123
+ const image = resolveMetadataUrl(getMediaUrl(meta.image) || getMediaUrl(page.media) || options.brand?.logo, baseUrl);
124
+ const canonicalBase = resolveMetadataUrl(page.href, baseUrl);
125
+ const canonical = canonicalBase ? `${canonicalBase}${getPaginationQuery(options.searchParams)}` : void 0;
126
+ const robots = getMetaRobots(meta);
127
+ const languages = {};
128
+ if (layout) {
129
+ getPageAlternates(page, layout, baseUrl).forEach((alternate) => {
130
+ languages[alternate.hrefLang] = alternate.href;
131
+ });
132
+ const xDefault = getPageXDefault(page, layout, baseUrl);
133
+ if (xDefault) languages["x-default"] = xDefault.href;
134
+ }
135
+ const images = image ? [{
136
+ url: image,
137
+ alt: title
138
+ }] : void 0;
139
+ const metadata = {
140
+ title,
141
+ description,
142
+ ...meta.keywords ? { keywords: meta.keywords } : {},
143
+ ...robots ? { robots } : {},
144
+ alternates: {
145
+ canonical,
146
+ ...Object.keys(languages).length > 0 ? { languages } : {}
147
+ },
148
+ openGraph: {
149
+ type: "website",
150
+ title,
151
+ description,
152
+ url: canonical,
153
+ siteName: options.brand?.name,
154
+ locale: layout?.locale ?? page.locale,
155
+ images
156
+ },
157
+ twitter: {
158
+ card: image ? "summary_large_image" : "summary",
159
+ title,
160
+ description,
161
+ images: image ? [image] : void 0,
162
+ ...options.brand?.twitterName ? {
163
+ site: options.brand.twitterName,
164
+ creator: options.brand.twitterName
165
+ } : {}
166
+ }
167
+ };
168
+ return options.extend ? options.extend(metadata, {
169
+ page,
170
+ layout
171
+ }) : metadata;
172
+ }
173
+ /**
174
+
175
+ * Next.js replaces nested `openGraph` and `twitter` objects instead of merging
176
+
177
+ * them, so a page that sets them would drop the layout's images, site name or
178
+
179
+ * handles. Fills the fields the page left empty from the parent metadata.
180
+
181
+ */
182
+ function inheritParentMetadata(metadata, parent) {
183
+ if (!parent) return metadata;
184
+ const openGraph = metadata.openGraph;
185
+ const twitter = metadata.twitter;
186
+ return {
187
+ ...metadata,
188
+ ...openGraph ? { openGraph: {
189
+ ...openGraph,
190
+ images: openGraph.images ?? parent.openGraph?.images,
191
+ siteName: openGraph.siteName ?? parent.openGraph?.siteName
192
+ } } : {},
193
+ ...twitter ? { twitter: {
194
+ ...twitter,
195
+ ...!twitter.images && parent.twitter?.images ? { card: parent.twitter.card } : {},
196
+ images: twitter.images ?? parent.twitter?.images,
197
+ site: twitter.site ?? parent.twitter?.site,
198
+ creator: twitter.creator ?? parent.twitter?.creator
199
+ } } : {}
200
+ };
201
+ }
202
+
203
+ //#endregion
204
+ export { buildPageMetadata, getPageAlternates, getPageXDefault, getPaginationQuery, inheritParentMetadata, resolveMetadataUrl, toPlainText };
205
+ //# sourceMappingURL=page-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-metadata.js","names":["html?: string | null","href: string | undefined","baseUrl: string","searchParams: PageMetadataSearchParams | undefined","key: string","searchParams?: PageMetadataSearchParams","page: Pick<IPage, 'alternates'>","layout: Pick<ILayout, 'markets'>","alternates: IAlternate[]","alternate: IRoute","layout: Pick<ILayout, 'markets' | 'locales'>","media: unknown","page: IPage","layout?: ILayout","options: PageMetadataOptions","languages: Record<string, string>","metadata: Metadata","parent?: ResolvedMetadata"],"sources":["../../src/lib/page-metadata.ts"],"sourcesContent":["import { defaultLocale, defaultMarket, getMetaRobots, ILayout, IMedia, IPage, IRoute } from '@websolutespa/bom-core';\r\nimport { convert } from 'html-to-text';\r\nimport type { Metadata, ResolvedMetadata } from 'next';\r\nimport type { IAlternate } from './meta-data';\r\nimport { getPublicUrl } from './utils';\r\n\r\nexport type PageMetadataBrand = {\r\n /** Site name, used for Open Graph `siteName`. */\r\n name: string;\r\n /** Fallback image when the page has no meta image or media. */\r\n logo?: string;\r\n /** Twitter handle, used for `twitter:site` and `twitter:creator`. */\r\n twitterName?: string;\r\n};\r\n\r\nexport type PageMetadataSearchParams = URLSearchParams | Record<string, string | string[] | undefined>;\r\n\r\nexport type PageMetadataContext = {\r\n page: IPage;\r\n layout?: ILayout;\r\n};\r\n\r\nexport type PageMetadataOptions = {\r\n brand?: PageMetadataBrand;\r\n /** Absolute origin for canonical, alternates and images. Default `NEXT_PUBLIC_URL`. */\r\n baseUrl?: string;\r\n /** Request search params; `pagination[page]` > 1 is kept in the canonical URL. */\r\n searchParams?: PageMetadataSearchParams;\r\n /** Per-page override applied to the computed metadata. */\r\n extend?: (metadata: Metadata, context: PageMetadataContext) => Metadata;\r\n};\r\n\r\ntype PageMetaFields = {\r\n title?: string;\r\n description?: string;\r\n keywords?: string;\r\n robots?: string;\r\n robot?: string;\r\n image?: IMedia | string | null;\r\n};\r\n\r\n/** Converts CMS HTML (titles, abstracts) to a single line of plain text. */\r\nexport function toPlainText(html?: string | null): string {\r\n if (!html) {\r\n return '';\r\n }\r\n return convert(html, {\r\n wordwrap: false,\r\n selectors: [\r\n { selector: 'a', options: { ignoreHref: true } },\r\n { selector: 'img', format: 'skip' },\r\n // keep the original case and drop list bullets: this text ends up in meta tags\r\n ...['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].map(selector => ({ selector, options: { uppercase: false } })),\r\n { selector: 'ul', options: { itemPrefix: ' ' } },\r\n { selector: 'table', format: 'dataTable', options: { uppercaseHeaderCells: false } },\r\n ],\r\n }).replace(/\\s+/g, ' ').trim();\r\n}\r\n\r\n/** Resolves a relative href against `baseUrl`; absolute URLs are returned as is. */\r\nexport function resolveMetadataUrl(href: string | undefined, baseUrl: string): string | undefined {\r\n if (!href) {\r\n return undefined;\r\n }\r\n return /^https?:\\/\\//.test(href) ? href : `${baseUrl}${href}`;\r\n}\r\n\r\nfunction getSearchParam(searchParams: PageMetadataSearchParams | undefined, key: string): string | undefined {\r\n if (!searchParams) {\r\n return undefined;\r\n }\r\n if (searchParams instanceof URLSearchParams) {\r\n return searchParams.get(key) ?? undefined;\r\n }\r\n const value = searchParams[key];\r\n return Array.isArray(value) ? value[0] : value;\r\n}\r\n\r\n/** Returns `?pagination[page]=N` for pages after the first, otherwise an empty string. */\r\nexport function getPaginationQuery(searchParams?: PageMetadataSearchParams): string {\r\n const value = getSearchParam(searchParams, 'pagination[page]');\r\n const page = value ? parseInt(value) : NaN;\r\n return Number.isInteger(page) && page > 1 ? `?pagination[page]=${page}` : '';\r\n}\r\n\r\n/**\r\n * Builds hreflang alternates from the page's routes. Markets with countries\r\n * produce one `locale-COUNTRY` entry per country; others use the locale only.\r\n */\r\nexport function getPageAlternates(page: Pick<IPage, 'alternates'>, layout: Pick<ILayout, 'markets'>, baseUrl: string): IAlternate[] {\r\n const alternates: IAlternate[] = [];\r\n (page.alternates || []).forEach((alternate: IRoute) => {\r\n const market = layout.markets.find(x => x.id === alternate.market);\r\n if (!market) {\r\n return;\r\n }\r\n const href = resolveMetadataUrl(alternate.id, baseUrl) as string;\r\n const countries = (market.countries || []) as Array<string | { alpha2Code?: string; id?: string }>;\r\n if (countries.length > 0) {\r\n countries.forEach(country => {\r\n const code = typeof country === 'string' ? country : (country.alpha2Code || country.id);\r\n if (code) {\r\n alternates.push({\r\n id: `${alternate.market}-${alternate.locale}-${code}`,\r\n href,\r\n hrefLang: `${alternate.locale}-${code.toUpperCase()}`,\r\n });\r\n }\r\n });\r\n } else {\r\n alternates.push({\r\n id: `${alternate.market}-${alternate.locale}`,\r\n href,\r\n hrefLang: alternate.locale,\r\n });\r\n }\r\n });\r\n return alternates;\r\n}\r\n\r\n/** The alternate for the default market and locale, used as `x-default`. */\r\nexport function getPageXDefault(page: Pick<IPage, 'alternates'>, layout: Pick<ILayout, 'markets' | 'locales'>, baseUrl: string): IAlternate | undefined {\r\n const market = layout.markets.find(x => x.isDefault)?.id || defaultMarket;\r\n const locale = layout.locales.find(x => x.isDefault)?.id || defaultLocale;\r\n const alternate = (page.alternates || []).find(x => x.market === market && x.locale === locale);\r\n if (!alternate) {\r\n return undefined;\r\n }\r\n return {\r\n id: `${alternate.market}-${alternate.locale}`,\r\n href: resolveMetadataUrl(alternate.id, baseUrl) as string,\r\n hrefLang: alternate.locale,\r\n };\r\n}\r\n\r\nfunction getMediaUrl(media: unknown): string | undefined {\r\n if (media && typeof media === 'object') {\r\n const { src, url } = media as { src?: string; url?: string };\r\n return src || url;\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Builds the Next.js `Metadata` for a page: plain-text title and description,\r\n * canonical (keeping pagination), hreflang alternates with `x-default`,\r\n * robots, keywords, Open Graph and Twitter card.\r\n */\r\nexport function buildPageMetadata(page: IPage, layout?: ILayout, options: PageMetadataOptions = {}): Metadata {\r\n const baseUrl = (options.baseUrl ?? getPublicUrl()).replace(/\\/$/, '');\r\n const meta = ((page as { meta?: PageMetaFields }).meta || {}) as PageMetaFields;\r\n const title = toPlainText(meta.title || page.title);\r\n const description = toPlainText(meta.description || (page as { abstract?: string }).abstract) || undefined;\r\n const image = resolveMetadataUrl(getMediaUrl(meta.image) || getMediaUrl((page as { media?: unknown }).media) || options.brand?.logo, baseUrl);\r\n const canonicalBase = resolveMetadataUrl(page.href, baseUrl);\r\n const canonical = canonicalBase ? `${canonicalBase}${getPaginationQuery(options.searchParams)}` : undefined;\r\n const robots = getMetaRobots(meta);\r\n\r\n const languages: Record<string, string> = {};\r\n if (layout) {\r\n getPageAlternates(page, layout, baseUrl).forEach(alternate => {\r\n languages[alternate.hrefLang] = alternate.href;\r\n });\r\n const xDefault = getPageXDefault(page, layout, baseUrl);\r\n if (xDefault) {\r\n languages['x-default'] = xDefault.href;\r\n }\r\n }\r\n\r\n const images = image ? [{ url: image, alt: title }] : undefined;\r\n const metadata: Metadata = {\r\n title,\r\n description,\r\n ...(meta.keywords ? { keywords: meta.keywords } : {}),\r\n ...(robots ? { robots } : {}),\r\n alternates: {\r\n canonical,\r\n ...(Object.keys(languages).length > 0 ? { languages } : {}),\r\n },\r\n openGraph: {\r\n type: 'website',\r\n title,\r\n description,\r\n url: canonical,\r\n siteName: options.brand?.name,\r\n locale: layout?.locale ?? page.locale,\r\n images,\r\n },\r\n twitter: {\r\n card: image ? 'summary_large_image' : 'summary',\r\n title,\r\n description,\r\n images: image ? [image] : undefined,\r\n ...(options.brand?.twitterName ? { site: options.brand.twitterName, creator: options.brand.twitterName } : {}),\r\n },\r\n };\r\n return options.extend ? options.extend(metadata, { page, layout }) : metadata;\r\n}\r\n\r\n/**\r\n * Next.js replaces nested `openGraph` and `twitter` objects instead of merging\r\n * them, so a page that sets them would drop the layout's images, site name or\r\n * handles. Fills the fields the page left empty from the parent metadata.\r\n */\r\nexport function inheritParentMetadata(metadata: Metadata, parent?: ResolvedMetadata): Metadata {\r\n if (!parent) {\r\n return metadata;\r\n }\r\n const openGraph = metadata.openGraph;\r\n const twitter = metadata.twitter;\r\n return {\r\n ...metadata,\r\n ...(openGraph ? {\r\n openGraph: {\r\n ...openGraph,\r\n images: openGraph.images ?? parent.openGraph?.images,\r\n siteName: openGraph.siteName ?? parent.openGraph?.siteName,\r\n },\r\n } : {}),\r\n ...(twitter ? {\r\n twitter: {\r\n ...twitter,\r\n // without a page image, the card type follows the inherited images\r\n ...(!twitter.images && parent.twitter?.images ? { card: parent.twitter.card } : {}),\r\n images: twitter.images ?? parent.twitter?.images,\r\n site: twitter.site ?? parent.twitter?.site,\r\n creator: twitter.creator ?? parent.twitter?.creator,\r\n } as Metadata['twitter'],\r\n } : {}),\r\n };\r\n}\r\n"],"mappings":"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"}