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

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 (147) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +61 -11
  3. package/dist/client/client.d.ts +2 -2
  4. package/dist/client/extra.d.ts +3 -3
  5. package/dist/client/extra.d.ts.map +1 -1
  6. package/dist/client/label.d.ts +3 -3
  7. package/dist/client/layout.d.ts +3 -3
  8. package/dist/client/layout.d.ts.map +1 -1
  9. package/dist/client/metadata.d.ts +10 -0
  10. package/dist/client/metadata.d.ts.map +1 -1
  11. package/dist/client/metadata.js +27 -46
  12. package/dist/client/metadata.js.map +1 -1
  13. package/dist/client/page.d.ts +4 -3
  14. package/dist/client/page.d.ts.map +1 -1
  15. package/dist/client/useApi.d.ts +7 -7
  16. package/dist/client/useCollectionApi.d.ts +6 -6
  17. package/dist/client/useCollectionApi.d.ts.map +1 -1
  18. package/dist/client/usePage.d.ts +1 -0
  19. package/dist/client/user.d.ts +15 -8
  20. package/dist/client/user.d.ts.map +1 -1
  21. package/dist/client/user.js +37 -14
  22. package/dist/client/user.js.map +1 -1
  23. package/dist/exports/client.d.ts +2 -2
  24. package/dist/exports/client.js +2 -2
  25. package/dist/exports/proxy.d.ts +3 -3
  26. package/dist/exports/proxy.js +3 -3
  27. package/dist/exports/server.d.ts +6 -4
  28. package/dist/exports/server.js +6 -4
  29. package/dist/lib/debug.js +14 -0
  30. package/dist/lib/debug.js.map +1 -0
  31. package/dist/lib/http.d.ts.map +1 -1
  32. package/dist/lib/http.js +2 -1
  33. package/dist/lib/http.js.map +1 -1
  34. package/dist/lib/meta-data.d.ts.map +1 -1
  35. package/dist/lib/page-metadata.d.ts +57 -0
  36. package/dist/lib/page-metadata.d.ts.map +1 -0
  37. package/dist/lib/page-metadata.js +205 -0
  38. package/dist/lib/page-metadata.js.map +1 -0
  39. package/dist/lib/ttl-cache.js +59 -0
  40. package/dist/lib/ttl-cache.js.map +1 -0
  41. package/dist/proxy/protected-route.d.ts.map +1 -1
  42. package/dist/proxy/protected-route.js +3 -2
  43. package/dist/proxy/protected-route.js.map +1 -1
  44. package/dist/proxy/route-auto-detection.d.ts +6 -1
  45. package/dist/proxy/route-auto-detection.d.ts.map +1 -1
  46. package/dist/proxy/route-auto-detection.js +68 -25
  47. package/dist/proxy/route-auto-detection.js.map +1 -1
  48. package/dist/proxy/route-proxy.d.ts +4 -1
  49. package/dist/proxy/route-proxy.d.ts.map +1 -1
  50. package/dist/proxy/route-proxy.js +96 -14
  51. package/dist/proxy/route-proxy.js.map +1 -1
  52. package/dist/server/handlers/registry.handler.d.ts.map +1 -1
  53. package/dist/server/handlers/registry.handler.js +77 -11
  54. package/dist/server/handlers/registry.handler.js.map +1 -1
  55. package/dist/server/handlers/route.handler.d.ts +12 -4
  56. package/dist/server/handlers/route.handler.d.ts.map +1 -1
  57. package/dist/server/handlers/route.handler.js +54 -39
  58. package/dist/server/handlers/route.handler.js.map +1 -1
  59. package/dist/server/helpers.d.ts +1 -4
  60. package/dist/server/helpers.d.ts.map +1 -1
  61. package/dist/server/json-ld.d.ts +33 -0
  62. package/dist/server/json-ld.d.ts.map +1 -0
  63. package/dist/server/json-ld.js +50 -0
  64. package/dist/server/json-ld.js.map +1 -0
  65. package/dist/server/layout.js +2 -1
  66. package/dist/server/layout.js.map +1 -1
  67. package/dist/server/lazy.js +8 -11
  68. package/dist/server/lazy.js.map +1 -1
  69. package/dist/server/metadata.d.ts +29 -10
  70. package/dist/server/metadata.d.ts.map +1 -1
  71. package/dist/server/metadata.js +36 -12
  72. package/dist/server/metadata.js.map +1 -1
  73. package/dist/server/mixer.d.ts +16 -7
  74. package/dist/server/mixer.d.ts.map +1 -1
  75. package/dist/server/mixer.js +102 -122
  76. package/dist/server/mixer.js.map +1 -1
  77. package/dist/types.d.ts +6 -3
  78. package/dist/types.d.ts.map +1 -1
  79. package/package.json +9 -6
  80. package/src/client/metadata.tsx +22 -57
  81. package/src/client/user.test.tsx +44 -0
  82. package/src/client/user.tsx +47 -14
  83. package/src/exports/server.ts +1 -0
  84. package/src/lib/debug.ts +11 -0
  85. package/src/lib/http.ts +2 -1
  86. package/src/lib/page-metadata.test.ts +126 -0
  87. package/src/lib/page-metadata.ts +231 -0
  88. package/src/lib/ttl-cache.ts +72 -0
  89. package/src/proxy/protected-route.ts +3 -2
  90. package/src/proxy/route-auto-detection.test.ts +55 -0
  91. package/src/proxy/route-auto-detection.ts +85 -24
  92. package/src/proxy/route-proxy.test.ts +93 -0
  93. package/src/proxy/route-proxy.ts +94 -18
  94. package/src/server/context.test.ts +91 -15
  95. package/src/server/handlers/registry.handler.test.ts +103 -11
  96. package/src/server/handlers/registry.handler.ts +98 -11
  97. package/src/server/handlers/route.handler.test.ts +39 -0
  98. package/src/server/handlers/route.handler.ts +61 -58
  99. package/src/server/json-ld.tsx +51 -0
  100. package/src/server/layout.ts +1 -0
  101. package/src/server/lazy.ts +6 -7
  102. package/src/server/metadata.ts +43 -77
  103. package/src/server/mixer.ts +108 -132
  104. package/src/server/request-data.test.ts +37 -0
  105. package/src/server/session/session.ts +22 -17
  106. package/src/types.ts +6 -3
  107. package/dist/_virtual/rolldown_runtime.js +0 -27
  108. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js +0 -76
  109. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js.map +0 -1
  110. package/dist/node_modules/deepmerge/dist/cjs.js +0 -94
  111. package/dist/node_modules/deepmerge/dist/cjs.js.map +0 -1
  112. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js +0 -105
  113. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js.map +0 -1
  114. package/dist/node_modules/dom-serializer/lib/esm/index.js +0 -141
  115. package/dist/node_modules/dom-serializer/lib/esm/index.js.map +0 -1
  116. package/dist/node_modules/domelementtype/lib/esm/index.js +0 -53
  117. package/dist/node_modules/domelementtype/lib/esm/index.js.map +0 -1
  118. package/dist/node_modules/domhandler/lib/esm/index.js +0 -131
  119. package/dist/node_modules/domhandler/lib/esm/index.js.map +0 -1
  120. package/dist/node_modules/domhandler/lib/esm/node.js +0 -308
  121. package/dist/node_modules/domhandler/lib/esm/node.js.map +0 -1
  122. package/dist/node_modules/entities/lib/esm/decode.js +0 -364
  123. package/dist/node_modules/entities/lib/esm/decode.js.map +0 -1
  124. package/dist/node_modules/entities/lib/esm/decode_codepoint.js +0 -59
  125. package/dist/node_modules/entities/lib/esm/decode_codepoint.js.map +0 -1
  126. package/dist/node_modules/entities/lib/esm/escape.js +0 -93
  127. package/dist/node_modules/entities/lib/esm/escape.js.map +0 -1
  128. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js +0 -6
  129. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js.map +0 -1
  130. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js +0 -6
  131. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js.map +0 -1
  132. package/dist/node_modules/html-to-text/lib/html-to-text.js +0 -1828
  133. package/dist/node_modules/html-to-text/lib/html-to-text.js.map +0 -1
  134. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js +0 -435
  135. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js.map +0 -1
  136. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js +0 -739
  137. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js.map +0 -1
  138. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js +0 -20
  139. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js.map +0 -1
  140. package/dist/node_modules/leac/lib/leac.js +0 -101
  141. package/dist/node_modules/leac/lib/leac.js.map +0 -1
  142. package/dist/node_modules/parseley/lib/parseley.js +0 -314
  143. package/dist/node_modules/parseley/lib/parseley.js.map +0 -1
  144. package/dist/node_modules/peberminta/lib/core.js +0 -149
  145. package/dist/node_modules/peberminta/lib/core.js.map +0 -1
  146. package/dist/node_modules/selderee/lib/selderee.js +0 -325
  147. package/dist/node_modules/selderee/lib/selderee.js.map +0 -1
@@ -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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"}
@@ -0,0 +1,59 @@
1
+ //#region src/lib/ttl-cache.ts
2
+ /**
3
+ * Small per-instance TTL cache with LRU eviction and in-flight de-duplication.
4
+ *
5
+ * Values are stored as promises so concurrent requests for the same key share
6
+ * one upstream call. Rejected promises are evicted immediately.
7
+ */
8
+ var TtlCache = class {
9
+ entries = /* @__PURE__ */ new Map();
10
+ constructor(maxSize) {
11
+ this.maxSize = maxSize;
12
+ }
13
+ get size() {
14
+ return this.entries.size;
15
+ }
16
+ async getOrSet(key, ttl, factory) {
17
+ const now = Date.now();
18
+ const entry = this.entries.get(key);
19
+ if (entry && entry.expires > now) {
20
+ this.entries.delete(key);
21
+ this.entries.set(key, entry);
22
+ return entry.value;
23
+ }
24
+ if (entry) this.entries.delete(key);
25
+ const value = factory();
26
+ const pending = {
27
+ value,
28
+ expires: Number.POSITIVE_INFINITY
29
+ };
30
+ this.set(key, pending);
31
+ try {
32
+ const resolved = await value;
33
+ const duration = ttl(resolved);
34
+ if (duration > 0 && this.entries.get(key) === pending) pending.expires = Date.now() + duration;
35
+ else if (this.entries.get(key) === pending) this.entries.delete(key);
36
+ return resolved;
37
+ } catch (error) {
38
+ if (this.entries.get(key) === pending) this.entries.delete(key);
39
+ throw error;
40
+ }
41
+ }
42
+ delete(key) {
43
+ this.entries.delete(key);
44
+ }
45
+ clear() {
46
+ this.entries.clear();
47
+ }
48
+ set(key, entry) {
49
+ if (this.entries.size >= this.maxSize) {
50
+ const oldest = this.entries.keys().next().value;
51
+ if (oldest !== void 0) this.entries.delete(oldest);
52
+ }
53
+ this.entries.set(key, entry);
54
+ }
55
+ };
56
+
57
+ //#endregion
58
+ export { TtlCache };
59
+ //# sourceMappingURL=ttl-cache.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"protected-route.d.ts","names":[],"sources":["../../src/proxy/protected-route.ts"],"sourcesContent":[],"mappings":";;;cAea,qBAAqB"}
1
+ {"version":3,"file":"protected-route.d.ts","names":[],"sources":["../../src/proxy/protected-route.ts"],"sourcesContent":[],"mappings":";;;cAgBa,qBAAqB"}
@@ -1,4 +1,5 @@
1
1
  import { getRouteTemplate } from "../lib/route.js";
2
+ import { debug } from "../lib/debug.js";
2
3
  import { localApiGet } from "../lib/http.js";
3
4
  import { getNextUrl } from "../server/utils.js";
4
5
  import { getRequestOrigin } from "./proxy.js";
@@ -14,7 +15,7 @@ const protectedRouteProxy = async ({ request, route }) => {
14
15
  if (route.isProtected) {
15
16
  const token = request.cookies.get("payload-token");
16
17
  const isAuthenticated = token;
17
- console.log("protectedRouteProxy.isAuthenticated", isAuthenticated);
18
+ debug("protectedRouteProxy.isAuthenticated", isAuthenticated);
18
19
  if (!isAuthenticated) {
19
20
  if (route.publicRedirect) {
20
21
  const redirectUrl = getPublicRedirectUrl(request, route.publicRedirect);
@@ -35,7 +36,7 @@ const protectedRouteProxy = async ({ request, route }) => {
35
36
  if (template) reqHeaders.set("x-template", template);
36
37
  reqHeaders.set("x-id", String(nextRoute.page));
37
38
  reqHeaders.set("x-forwarded-pathname", nextUrl.pathname);
38
- console.log("protectedRouteProxy.Protected", pathname, ">", nextUrl.pathname);
39
+ debug("protectedRouteProxy.Protected", pathname, ">", nextUrl.pathname);
39
40
  const response = NextResponse.rewrite(nextUrl, { request: { headers: reqHeaders } });
40
41
  return response;
41
42
  }