@websolutespa/bom-next 3.0.2 → 4.0.0-next.1

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 (264) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/README.md +28 -0
  3. package/dist/_virtual/{_rolldown/runtime.js → rolldown_runtime.js} +7 -4
  4. package/dist/client/client.d.ts.map +1 -1
  5. package/dist/client/client.js +17 -6
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/client/extra.d.ts +3 -3
  8. package/dist/client/extra.d.ts.map +1 -1
  9. package/dist/client/extra.js +10 -5
  10. package/dist/client/extra.js.map +1 -1
  11. package/dist/client/label.d.ts +3 -3
  12. package/dist/client/label.d.ts.map +1 -1
  13. package/dist/client/label.js +10 -5
  14. package/dist/client/label.js.map +1 -1
  15. package/dist/client/layout.d.ts +3 -3
  16. package/dist/client/layout.d.ts.map +1 -1
  17. package/dist/client/layout.js +10 -5
  18. package/dist/client/layout.js.map +1 -1
  19. package/dist/client/local.collection.d.ts.map +1 -1
  20. package/dist/client/local.collection.js +4 -2
  21. package/dist/client/local.collection.js.map +1 -1
  22. package/dist/client/metadata.d.ts +3 -3
  23. package/dist/client/metadata.d.ts.map +1 -1
  24. package/dist/client/metadata.js +48 -40
  25. package/dist/client/metadata.js.map +1 -1
  26. package/dist/client/page.d.ts +3 -3
  27. package/dist/client/page.d.ts.map +1 -1
  28. package/dist/client/page.js +10 -5
  29. package/dist/client/page.js.map +1 -1
  30. package/dist/client/useApi.d.ts +7 -7
  31. package/dist/client/useApi.d.ts.map +1 -1
  32. package/dist/client/useApi.js +2 -1
  33. package/dist/client/useApi.js.map +1 -1
  34. package/dist/client/useCollectionApi.d.ts +6 -6
  35. package/dist/client/useCollectionApi.d.ts.map +1 -1
  36. package/dist/client/useCollectionApi.js +4 -2
  37. package/dist/client/useCollectionApi.js.map +1 -1
  38. package/dist/client/useExtra.d.ts.map +1 -1
  39. package/dist/client/useExtra.js +5 -2
  40. package/dist/client/useExtra.js.map +1 -1
  41. package/dist/client/useHttp.d.ts.map +1 -1
  42. package/dist/client/useHttp.js +9 -6
  43. package/dist/client/useHttp.js.map +1 -1
  44. package/dist/client/useLabel.d.ts.map +1 -1
  45. package/dist/client/useLabel.js +5 -2
  46. package/dist/client/useLabel.js.map +1 -1
  47. package/dist/client/useLayout.d.ts.map +1 -1
  48. package/dist/client/useLayout.js +5 -2
  49. package/dist/client/useLayout.js.map +1 -1
  50. package/dist/client/useLink.d.ts.map +1 -1
  51. package/dist/client/useLink.js +7 -3
  52. package/dist/client/useLink.js.map +1 -1
  53. package/dist/client/useMetaData.d.ts.map +1 -1
  54. package/dist/client/useMetaData.js +5 -2
  55. package/dist/client/useMetaData.js.map +1 -1
  56. package/dist/client/usePage.d.ts.map +1 -1
  57. package/dist/client/usePage.js +5 -2
  58. package/dist/client/usePage.js.map +1 -1
  59. package/dist/client/useUser.d.ts.map +1 -1
  60. package/dist/client/useUser.js +5 -2
  61. package/dist/client/useUser.js.map +1 -1
  62. package/dist/client/user.d.ts.map +1 -1
  63. package/dist/client/user.js +17 -11
  64. package/dist/client/user.js.map +1 -1
  65. package/dist/exports/client.js +2 -1
  66. package/dist/exports/instrumentation.js +2 -1
  67. package/dist/exports/proxy.js +2 -1
  68. package/dist/exports/server.d.ts +5 -4
  69. package/dist/exports/server.js +7 -5
  70. package/dist/index.d.ts +3 -3
  71. package/dist/index.js +3 -2
  72. package/dist/instrumentation/client.d.ts.map +1 -1
  73. package/dist/instrumentation/client.js +2 -1
  74. package/dist/instrumentation/client.js.map +1 -1
  75. package/dist/instrumentation/sentry/capture.requestError.js +4 -1
  76. package/dist/instrumentation/sentry/capture.requestError.js.map +1 -1
  77. package/dist/instrumentation/sentry/capture.routerTransitionStart.js +2 -1
  78. package/dist/instrumentation/sentry/capture.routerTransitionStart.js.map +1 -1
  79. package/dist/instrumentation/sentry/config.client.js +10 -2
  80. package/dist/instrumentation/sentry/config.client.js.map +1 -0
  81. package/dist/instrumentation/sentry/config.edge.js +10 -2
  82. package/dist/instrumentation/sentry/config.edge.js.map +1 -0
  83. package/dist/instrumentation/sentry/config.server.js +10 -2
  84. package/dist/instrumentation/sentry/config.server.js.map +1 -0
  85. package/dist/instrumentation/server.d.ts.map +1 -1
  86. package/dist/instrumentation/server.js +8 -5
  87. package/dist/instrumentation/server.js.map +1 -1
  88. package/dist/instrumentation/types.d.ts.map +1 -1
  89. package/dist/lib/breadcrumb.d.ts.map +1 -1
  90. package/dist/lib/breadcrumb.js +6 -1
  91. package/dist/lib/breadcrumb.js.map +1 -1
  92. package/dist/lib/const.d.ts +14 -2
  93. package/dist/lib/const.d.ts.map +1 -1
  94. package/dist/lib/const.js +24 -2
  95. package/dist/lib/const.js.map +1 -1
  96. package/dist/lib/http.d.ts +9 -0
  97. package/dist/lib/http.d.ts.map +1 -1
  98. package/dist/lib/http.js +27 -4
  99. package/dist/lib/http.js.map +1 -1
  100. package/dist/lib/label.d.ts.map +1 -1
  101. package/dist/lib/label.js +4 -3
  102. package/dist/lib/label.js.map +1 -1
  103. package/dist/lib/layout.defaults.d.ts.map +1 -1
  104. package/dist/lib/layout.defaults.js +1 -1
  105. package/dist/lib/layout.defaults.js.map +1 -1
  106. package/dist/lib/link.d.ts.map +1 -1
  107. package/dist/lib/link.js +2 -1
  108. package/dist/lib/link.js.map +1 -1
  109. package/dist/lib/meta-data.d.ts.map +1 -1
  110. package/dist/lib/open-graph.d.ts.map +1 -1
  111. package/dist/lib/pagination.d.ts.map +1 -1
  112. package/dist/lib/pagination.js +4 -2
  113. package/dist/lib/pagination.js.map +1 -1
  114. package/dist/lib/route.d.ts.map +1 -1
  115. package/dist/lib/route.js +9 -3
  116. package/dist/lib/route.js.map +1 -1
  117. package/dist/lib/store.d.ts.map +1 -1
  118. package/dist/lib/store.js +31 -31
  119. package/dist/lib/store.js.map +1 -1
  120. package/dist/lib/structured-data.d.ts.map +1 -1
  121. package/dist/lib/twitter-card.d.ts.map +1 -1
  122. package/dist/lib/utils.d.ts.map +1 -1
  123. package/dist/lib/utils.js +3 -2
  124. package/dist/lib/utils.js.map +1 -1
  125. package/dist/mock.d.ts.map +1 -1
  126. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js +3 -2
  127. package/dist/node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.js.map +1 -1
  128. package/dist/node_modules/deepmerge/dist/cjs.js +17 -9
  129. package/dist/node_modules/deepmerge/dist/cjs.js.map +1 -1
  130. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js +2 -2
  131. package/dist/node_modules/dom-serializer/lib/esm/foreignNames.js.map +1 -1
  132. package/dist/node_modules/dom-serializer/lib/esm/index.js +10 -15
  133. package/dist/node_modules/dom-serializer/lib/esm/index.js.map +1 -1
  134. package/dist/node_modules/domelementtype/lib/esm/index.js +12 -12
  135. package/dist/node_modules/domelementtype/lib/esm/index.js.map +1 -1
  136. package/dist/node_modules/domhandler/lib/esm/index.js +4 -3
  137. package/dist/node_modules/domhandler/lib/esm/index.js.map +1 -1
  138. package/dist/node_modules/domhandler/lib/esm/node.js +8 -7
  139. package/dist/node_modules/domhandler/lib/esm/node.js.map +1 -1
  140. package/dist/node_modules/entities/lib/esm/decode.js +33 -32
  141. package/dist/node_modules/entities/lib/esm/decode.js.map +1 -1
  142. package/dist/node_modules/entities/lib/esm/decode_codepoint.js +5 -5
  143. package/dist/node_modules/entities/lib/esm/decode_codepoint.js.map +1 -1
  144. package/dist/node_modules/entities/lib/esm/escape.js +14 -6
  145. package/dist/node_modules/entities/lib/esm/escape.js.map +1 -1
  146. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js +2 -2
  147. package/dist/node_modules/entities/lib/esm/generated/decode-data-html.js.map +1 -1
  148. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js +2 -2
  149. package/dist/node_modules/entities/lib/esm/generated/decode-data-xml.js.map +1 -1
  150. package/dist/node_modules/html-to-text/lib/html-to-text.js +45 -33
  151. package/dist/node_modules/html-to-text/lib/html-to-text.js.map +1 -1
  152. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js +25 -25
  153. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Parser.js.map +1 -1
  154. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js +76 -73
  155. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/Tokenizer.js.map +1 -1
  156. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js +4 -3
  157. package/dist/node_modules/html-to-text/node_modules/htmlparser2/lib/esm/index.js.map +1 -1
  158. package/dist/node_modules/leac/lib/leac.js +72 -72
  159. package/dist/node_modules/leac/lib/leac.js.map +1 -1
  160. package/dist/node_modules/parseley/lib/parseley.js +37 -33
  161. package/dist/node_modules/parseley/lib/parseley.js.map +1 -1
  162. package/dist/node_modules/peberminta/lib/core.js +4 -4
  163. package/dist/node_modules/peberminta/lib/core.js.map +1 -1
  164. package/dist/node_modules/selderee/lib/selderee.js +16 -10
  165. package/dist/node_modules/selderee/lib/selderee.js.map +1 -1
  166. package/dist/proxy/protected-route.d.ts.map +1 -1
  167. package/dist/proxy/protected-route.js +11 -6
  168. package/dist/proxy/protected-route.js.map +1 -1
  169. package/dist/proxy/proxy.d.ts +5 -4
  170. package/dist/proxy/proxy.d.ts.map +1 -1
  171. package/dist/proxy/proxy.js +26 -6
  172. package/dist/proxy/proxy.js.map +1 -1
  173. package/dist/proxy/route-auto-detection.d.ts.map +1 -1
  174. package/dist/proxy/route-auto-detection.js +26 -14
  175. package/dist/proxy/route-auto-detection.js.map +1 -1
  176. package/dist/proxy/route-proxy.d.ts.map +1 -1
  177. package/dist/proxy/route-proxy.js +22 -5
  178. package/dist/proxy/route-proxy.js.map +1 -1
  179. package/dist/server/context.d.ts +31 -15
  180. package/dist/server/context.d.ts.map +1 -1
  181. package/dist/server/context.js +156 -69
  182. package/dist/server/context.js.map +1 -1
  183. package/dist/server/handlers/auth.handler.d.ts.map +1 -1
  184. package/dist/server/handlers/auth.handler.js +4 -2
  185. package/dist/server/handlers/auth.handler.js.map +1 -1
  186. package/dist/server/handlers/captions.handler.d.ts.map +1 -1
  187. package/dist/server/handlers/captions.handler.js +4 -2
  188. package/dist/server/handlers/captions.handler.js.map +1 -1
  189. package/dist/server/handlers/collection.handler.d.ts.map +1 -1
  190. package/dist/server/handlers/collection.handler.js +2 -1
  191. package/dist/server/handlers/collection.handler.js.map +1 -1
  192. package/dist/server/handlers/error-response.js +6 -2
  193. package/dist/server/handlers/error-response.js.map +1 -1
  194. package/dist/server/handlers/index.handler.d.ts.map +1 -1
  195. package/dist/server/handlers/index.handler.js +2 -1
  196. package/dist/server/handlers/index.handler.js.map +1 -1
  197. package/dist/server/handlers/preview.handler.d.ts.map +1 -1
  198. package/dist/server/handlers/preview.handler.js +6 -3
  199. package/dist/server/handlers/preview.handler.js.map +1 -1
  200. package/dist/server/handlers/registry.handler.d.ts.map +1 -1
  201. package/dist/server/handlers/registry.handler.js +14 -9
  202. package/dist/server/handlers/registry.handler.js.map +1 -1
  203. package/dist/server/handlers/route.handler.d.ts.map +1 -1
  204. package/dist/server/handlers/route.handler.js +12 -3
  205. package/dist/server/handlers/route.handler.js.map +1 -1
  206. package/dist/server/handlers/sitemap.handler.d.ts.map +1 -1
  207. package/dist/server/handlers/sitemap.handler.js +12 -9
  208. package/dist/server/handlers/sitemap.handler.js.map +1 -1
  209. package/dist/server/handlers/static-params.handler.d.ts.map +1 -1
  210. package/dist/server/handlers/static-params.handler.js +13 -8
  211. package/dist/server/handlers/static-params.handler.js.map +1 -1
  212. package/dist/server/handlers/vars.handler.d.ts.map +1 -1
  213. package/dist/server/handlers/vars.handler.js +5 -4
  214. package/dist/server/handlers/vars.handler.js.map +1 -1
  215. package/dist/server/helpers.d.ts +50 -0
  216. package/dist/server/helpers.d.ts.map +1 -0
  217. package/dist/server/helpers.js +60 -0
  218. package/dist/server/helpers.js.map +1 -0
  219. package/dist/server/layout.js +69 -34
  220. package/dist/server/layout.js.map +1 -1
  221. package/dist/server/lazy.d.ts +1 -0
  222. package/dist/server/lazy.d.ts.map +1 -1
  223. package/dist/server/lazy.js +3 -2
  224. package/dist/server/lazy.js.map +1 -1
  225. package/dist/server/local-api.d.ts.map +1 -1
  226. package/dist/server/local-api.js +12 -6
  227. package/dist/server/local-api.js.map +1 -1
  228. package/dist/server/metadata.d.ts +8 -5
  229. package/dist/server/metadata.d.ts.map +1 -1
  230. package/dist/server/metadata.js +21 -7
  231. package/dist/server/metadata.js.map +1 -1
  232. package/dist/server/mixer-payload.d.ts +1 -1
  233. package/dist/server/mixer-payload.d.ts.map +1 -1
  234. package/dist/server/mixer.d.ts +48 -6
  235. package/dist/server/mixer.d.ts.map +1 -1
  236. package/dist/server/mixer.js +235 -68
  237. package/dist/server/mixer.js.map +1 -1
  238. package/dist/server/page.d.ts +21 -5
  239. package/dist/server/page.d.ts.map +1 -1
  240. package/dist/server/page.js +74 -15
  241. package/dist/server/page.js.map +1 -1
  242. package/dist/server/utils.d.ts.map +1 -1
  243. package/dist/server/utils.js +4 -2
  244. package/dist/server/utils.js.map +1 -1
  245. package/dist/server/utmz.d.ts.map +1 -1
  246. package/dist/server/utmz.js +1 -1
  247. package/dist/server/utmz.js.map +1 -1
  248. package/dist/types.d.ts +79 -8
  249. package/dist/types.d.ts.map +1 -1
  250. package/package.json +16 -15
  251. package/src/exports/server.ts +1 -0
  252. package/src/lib/const.ts +15 -0
  253. package/src/lib/http.ts +15 -4
  254. package/src/proxy/proxy.ts +11 -4
  255. package/src/server/context.test.ts +467 -0
  256. package/src/server/context.ts +237 -78
  257. package/src/server/context.type-test.ts +106 -0
  258. package/src/server/helpers.ts +58 -0
  259. package/src/server/layout.ts +45 -39
  260. package/src/server/lazy.ts +1 -1
  261. package/src/server/metadata.ts +15 -7
  262. package/src/server/mixer.ts +57 -9
  263. package/src/server/page.ts +62 -17
  264. package/src/types.ts +109 -7
@@ -1,22 +1,30 @@
1
1
  import type { ResolvingMetadata } from 'next';
2
2
  import type { CollectionSlug, SanitizedConfig } from 'payload';
3
- import type { PageParams } from '../types';
3
+ import type { LayoutOption, PageContextOptions, PageParams, PageSelect } from '../types';
4
4
  import { createPageContext } from './context';
5
5
 
6
6
  /**
7
7
  * Returns a generateMetadata function for a given page collection slug.
8
- * Uses bowl's createPageContext (Payload local API) instead of the store abstraction.
8
+ *
9
+ * It uses the same defaults as `createPageContext`, so the page read is
10
+ * memoized and shared with the page component within the same request.
11
+ * Pass the same options used by the page (depth, select, headers…) to hit that cache.
9
12
  */
10
- export function getMetadata<TSlug extends CollectionSlug>(slug: TSlug, config: Promise<SanitizedConfig>) {
13
+ export function getMetadata<TSlug extends CollectionSlug, TSelect extends PageSelect<TSlug> = undefined>(
14
+ slug: TSlug,
15
+ config: Promise<SanitizedConfig>,
16
+ options?: Omit<PageContextOptions<TSlug, TSelect, LayoutOption>, 'config'>
17
+ ) {
11
18
  return async function generateMetadata(
12
19
  props: Readonly<{ params: Promise<PageParams>; }>,
13
- parent: ResolvingMetadata
20
+ _parent: ResolvingMetadata
14
21
  ) {
15
22
  const { market, locale, id } = await props.params;
16
- const { page } = await createPageContext(market, locale, slug, id, config);
23
+ const { page } = await createPageContext<TSlug, TSelect, LayoutOption>(market, locale, slug, id, { ...options, config });
24
+ const doc = page as { title?: string; abstract?: string; meta?: { title?: string; description?: string } } | undefined;
17
25
  return {
18
- title: page!.meta?.title || page!.title,
19
- description: page!.meta?.description || page!.abstract,
26
+ title: doc?.meta?.title || doc?.title,
27
+ description: doc?.meta?.description || doc?.abstract,
20
28
  };
21
29
  };
22
30
  }
@@ -1,11 +1,12 @@
1
1
  import { collectRoutes, defaultLocale, defaultMarket, ICategory, ILocale, IMarket, IMedia, IRoute, isDevelopment, isObject } from '@websolutespa/bom-core';
2
2
  import type { Config, User } from '@websolutespa/payload-plugin-bowl';
3
3
  import { InMemoryCache } from '@websolutespa/payload-plugin-bowl';
4
+ import { draftMode, headers as getHeaders } from 'next/headers';
4
5
  import { NextRequest } from 'next/server';
5
6
  import { BasePayload, getPayload as getPayload_, SanitizedConfig, TypedFallbackLocale, TypedLocale } from 'payload';
6
7
  import { cache } from 'react';
7
8
  import { guest } from '../lib/const';
8
- import type { BaseOptions, IContext, MixerContext, ReadonlyHeaders } from '../types';
9
+ import type { BaseOptions, DraftOption, IContext, MixerContext, ReadOptions, ReadonlyHeaders } from '../types';
9
10
  import type { MixerPayload } from './mixer-payload';
10
11
 
11
12
  // !!! todo check when to use in memory cache
@@ -269,11 +270,28 @@ export async function getMixerContext({
269
270
  return context;
270
271
  }
271
272
 
273
+ /**
274
+ * Resolves a draft option to a boolean.
275
+ * `'auto'` reads Next's `draftMode()`; outside a request scope (build, scripts) it resolves to false.
276
+ */
277
+ export async function resolveDraft(draft: DraftOption = 'auto'): Promise<boolean> {
278
+ if (typeof draft === 'boolean') {
279
+ return draft;
280
+ }
281
+ try {
282
+ return (await draftMode()).isEnabled;
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+
272
288
  /**
273
289
  * Runtime (request-time) payload accessor.
274
290
  *
275
291
  * By default, operates as the guest user for static generation. For auth:
276
292
  * call getHeaders() directly in your page/route/layout, then pass it here.
293
+ * In draft mode the request headers are read automatically, so drafts are
294
+ * read as the authenticated user rather than the guest.
277
295
  *
278
296
  * Example:
279
297
  * const headers = await getHeaders();
@@ -282,13 +300,23 @@ export async function getMixerContext({
282
300
  export async function getPayload({
283
301
  config,
284
302
  headers,
303
+ draft = false,
285
304
  }: {
286
305
  config: Promise<SanitizedConfig>;
287
306
  headers?: ReadonlyHeaders;
307
+ draft?: boolean;
288
308
  }) {
289
309
  const payload = await getPayload_({ config });
290
310
  await setContext({ payload: payload as MixerPayload });
291
311
 
312
+ if (!headers && draft) {
313
+ try {
314
+ headers = await getHeaders() as ReadonlyHeaders;
315
+ } catch {
316
+ // outside a request scope: keep the guest user
317
+ }
318
+ }
319
+
292
320
  let user: User & Config['user'] = guest;
293
321
  if (headers) {
294
322
  try {
@@ -296,17 +324,15 @@ export async function getPayload({
296
324
  if (auth.user) {
297
325
  user = auth.user as typeof user;
298
326
  }
299
- // console.log('getPayload', user, token);
300
327
  } catch (error) {
301
328
  console.log('getPayload.error', error);
302
329
  }
303
330
  }
304
331
 
305
- const draft = false;
306
-
307
332
  return {
308
333
  payload: payload as MixerPayload,
309
334
  user,
335
+ draft,
310
336
  getContext: (market: string, locale: string) => getMixerContext({ payload, market, locale, user, draft }),
311
337
  };
312
338
  }
@@ -326,21 +352,43 @@ export function getAuthHeaders(token: { value: string } | undefined): Headers {
326
352
  }
327
353
 
328
354
  /**
329
- * Produces the common option block shared across all payload.find / payload.findGlobal calls.
355
+ * Produces the read options shared across payload.find / findByID / findGlobal calls.
330
356
  * For payload.find, add pagination: false and limit: 10000 at the call site.
331
- * For payload.findByID, spread directly — extra find-only fields are ignored.
357
+ * `select`, `populate` and `joins` are only included when provided.
332
358
  */
333
- export function buildBaseOptions(user: any, locale: string, draft?: boolean) {
359
+ export function buildReadOptions({
360
+ user,
361
+ locale,
362
+ draft = false,
363
+ depth,
364
+ select,
365
+ populate,
366
+ joins,
367
+ }: ReadOptions & {
368
+ user: any;
369
+ locale: string;
370
+ }) {
334
371
  return {
335
372
  locale: locale as TypedLocale,
336
373
  fallbackLocale: defaultLocale as TypedFallbackLocale,
337
- draft: draft ?? false,
338
- depth: 0,
374
+ draft,
375
+ depth,
339
376
  user,
340
377
  overrideAccess: false,
378
+ ...(select !== undefined ? { select } : {}),
379
+ ...(populate !== undefined ? { populate } : {}),
380
+ // joins are collection specific: typed by the caller's options, untyped here
381
+ ...(joins !== undefined ? { joins } as {} : {}),
341
382
  };
342
383
  }
343
384
 
385
+ /**
386
+ * @deprecated Use `buildReadOptions({ user, locale, draft, depth })`.
387
+ * Kept for backward compatibility: reads at depth 0.
388
+ */
389
+ export function buildBaseOptions(user: any, locale: string, draft?: boolean) {
390
+ return buildReadOptions({ user, locale, draft, depth: 0 });
391
+ }
344
392
 
345
393
  /**
346
394
  * Payload proxy example
@@ -1,13 +1,50 @@
1
- 'use server';
1
+ import 'server-only';
2
2
 
3
3
  import { getCategorySegments, ICategorized, ICategory, IRoute, IRouteLink } from '@websolutespa/bom-core';
4
4
  import type { User } from '@websolutespa/payload-plugin-bowl';
5
- import type { CollectionSlug } from 'payload';
5
+ import type { CollectionSlug, SelectType } from 'payload';
6
6
  import { buildBreadcrumb } from '../lib/breadcrumb';
7
- import type { MixerContext, TypedPage } from '../types';
8
- import { buildBaseOptions } from './mixer';
7
+ import type { MixerContext, PageEnvelopeField, PageSelect, ReadOptions, TypedPage } from '../types';
8
+ import { buildReadOptions } from './mixer';
9
9
  import type { MixerPayload } from './mixer-payload';
10
10
 
11
+ /**
12
+ * Fields `getPage` needs to build the envelope (href, alternates, breadcrumb,
13
+ * segments) and that templates rely on (meta, template). They are always
14
+ * kept, whatever `select` the caller passes.
15
+ */
16
+ export const PAGE_ENVELOPE_FIELDS = ['id', 'slug', 'title', 'category', 'markets', 'meta', 'template'] as const satisfies readonly PageEnvelopeField[];
17
+
18
+ function isExcludeSelect(select: SelectType): boolean {
19
+ return Object.values(select).some(value =>
20
+ value === false || (typeof value === 'object' && value !== null && isExcludeSelect(value as SelectType))
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Merges the envelope fields into a Payload select.
26
+ * - include mode (`true` values): envelope keys are added.
27
+ * - exclude mode (`false` values): envelope keys are removed from the exclusion list.
28
+ */
29
+ export function withEnvelopeSelect<T extends SelectType | undefined>(select: T): T {
30
+ if (!select || Object.keys(select).length === 0) {
31
+ return select;
32
+ }
33
+ const merged: Record<string, unknown> = { ...select };
34
+ if (isExcludeSelect(select)) {
35
+ for (const key of PAGE_ENVELOPE_FIELDS) {
36
+ delete merged[key];
37
+ }
38
+ } else {
39
+ for (const key of PAGE_ENVELOPE_FIELDS) {
40
+ if (merged[key] === undefined) {
41
+ merged[key] = true;
42
+ }
43
+ }
44
+ }
45
+ return merged as T;
46
+ }
47
+
11
48
  /**
12
49
  * Fetches a single page document by ID using Payload local API and assembles
13
50
  * the IPage envelope (routes, alternates, breadcrumb, parentRoute).
@@ -16,8 +53,12 @@ import type { MixerPayload } from './mixer-payload';
16
53
  * - routes/alternates : mixerContext.routes filtered by schema+id
17
54
  * - segments : getCategorySegments() from bom-core (pure)
18
55
  * - breadcrumb : findRouteOfCategory() from bom-core + mixerContext.currentRoutes
56
+ *
57
+ * Returns undefined when the document is not found; any other error is logged and rethrown.
58
+ * The last argument accepts read options (depth, draft, select, populate, joins);
59
+ * a boolean is still accepted as the legacy `draftMode` flag (depth 0).
19
60
  */
20
- export async function getPage<TSlug extends CollectionSlug>(
61
+ export async function getPage<TSlug extends CollectionSlug, TSelect extends PageSelect<TSlug> = undefined>(
21
62
  slug: TSlug,
22
63
  id: string,
23
64
  market: string,
@@ -25,19 +66,22 @@ export async function getPage<TSlug extends CollectionSlug>(
25
66
  payload: MixerPayload,
26
67
  user: User,
27
68
  mixerContext: MixerContext,
28
- draftMode?: boolean
29
- ): Promise<TypedPage<TSlug> | undefined> {
69
+ readOptions?: ReadOptions | boolean
70
+ ): Promise<TypedPage<TSlug, TSelect> | undefined> {
71
+ const options: ReadOptions = typeof readOptions === 'object'
72
+ ? readOptions
73
+ : { draft: readOptions, depth: 0 };
74
+ const read = buildReadOptions({
75
+ ...options,
76
+ select: withEnvelopeSelect(options.select),
77
+ user,
78
+ locale,
79
+ });
30
80
  try {
31
- const base = buildBaseOptions(user, locale, draftMode);
32
81
  const page = await payload.findByID({
82
+ ...read,
33
83
  collection: slug,
34
84
  id,
35
- locale: base.locale,
36
- fallbackLocale: base.fallbackLocale,
37
- draft: base.draft,
38
- depth: base.depth,
39
- user: base.user,
40
- overrideAccess: base.overrideAccess,
41
85
  disableErrors: true,
42
86
  context: mixerContext,
43
87
  });
@@ -69,13 +113,14 @@ export async function getPage<TSlug extends CollectionSlug>(
69
113
  alternates,
70
114
  breadcrumb,
71
115
  parentRoute,
72
- } as unknown as TypedPage<TSlug>;
116
+ } as unknown as TypedPage<TSlug, TSelect>;
73
117
  } else {
74
118
  console.log('PageService.getPage.notfound', slug, id, locale);
75
119
  return undefined;
76
120
  }
77
- } catch {
78
- return undefined;
121
+ } catch (error) {
122
+ console.error('PageService.getPage.error', slug, id, locale, error);
123
+ throw error;
79
124
  }
80
125
  }
81
126
 
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ICategorized, IComponent, ILayout, ILocale, IMarket, IPage, IPrettify, IRoute, PageProps } from '@websolutespa/bom-core';
2
2
  import type { Category, EndUser, User } from '@websolutespa/payload-plugin-bowl';
3
3
  import type { DynamicOptions, Loader } from 'next/dynamic';
4
- import type { BasePayload, CollectionSlug, DataFromCollectionSlug, SanitizedConfig, TypedFallbackLocale, TypedLocale } from 'payload';
4
+ import type { BasePayload, CollectionSlug, DataFromCollectionSlug, JoinQuery, PopulateType, SanitizedConfig, SelectType, TransformCollectionWithSelect, TypedCollectionSelect, TypedFallbackLocale, TypedLocale } from 'payload';
5
5
  import type { ComponentType, ReactNode } from 'react';
6
6
  import type { BowlDecorator, MixerPayload } from './server/mixer-payload';
7
7
 
@@ -114,14 +114,116 @@ export type IContext = {
114
114
  context: MixerContext;
115
115
  }
116
116
 
117
- export type PageDocument<TSlug extends CollectionSlug> = DataFromCollectionSlug<TSlug>;
117
+ /** `true` / `false` forces draft mode, `'auto'` reads Next's `draftMode()`. */
118
+ export type DraftOption = boolean | 'auto';
119
+
120
+ /** Select accepted for a page collection, or `undefined` for the full document. */
121
+ export type PageSelect<TSlug extends CollectionSlug> = TypedCollectionSelect[TSlug] | undefined;
122
+
123
+ /**
124
+ * A generated page collection document, narrowed by `select` when one is given.
125
+ * Without a select the full generated document type is returned.
126
+ */
127
+ export type PageDocument<
128
+ TSlug extends CollectionSlug,
129
+ TSelect = undefined,
130
+ > = TSelect extends SelectType ? TransformCollectionWithSelect<TSlug, TSelect> : DataFromCollectionSlug<TSlug>;
131
+
132
+ /** Fields always read by `createPageContext`, whatever `select` is passed (see PAGE_ENVELOPE_FIELDS). */
133
+ export type PageEnvelopeField = 'id' | 'slug' | 'title' | 'category' | 'markets' | 'meta' | 'template';
134
+
135
+ type SelectInclude = { [key: string]: SelectInclude | true };
136
+
137
+ /** Type counterpart of `withEnvelopeSelect`: the envelope fields are always part of the result. */
138
+ export type WithEnvelopeSelect<TSelect> = TSelect extends SelectInclude
139
+ ? TSelect & { [K in PageEnvelopeField]: true }
140
+ : Omit<TSelect, PageEnvelopeField>;
141
+
142
+ /** Envelope added by `getPage` to every page document. */
143
+ export type PageEnvelope = Pick<IPage, 'schema' | 'alternates' | 'breadcrumb' | 'href' | 'parentRoute'>;
144
+
145
+ /**
146
+ * A generated page collection document enriched with Bowl's page envelope.
147
+ * With a `select`, only the selected fields (plus the envelope fields) are typed.
148
+ */
149
+ export type TypedPage<
150
+ TSlug extends CollectionSlug,
151
+ TSelect extends PageSelect<TSlug> = undefined,
152
+ > = [TSelect] extends [SelectType]
153
+ ? BowlDecorator<PageDocument<TSlug, WithEnvelopeSelect<TSelect>>> & PageEnvelope
154
+ : BowlDecorator<PageDocument<TSlug>> & IPage<ICategorized>;
155
+
156
+ /**
157
+ * Narrows relationship / upload fields that are known to be populated
158
+ * (depth >= 1) from `string | T` (or `number | T`) to `T`.
159
+ *
160
+ * @example
161
+ * const media = page.media as Populated<typeof page.media>;
162
+ */
163
+ export type Populated<T> = T extends readonly (infer TItem)[]
164
+ ? Populated<TItem>[]
165
+ : Exclude<T, string | number>;
166
+
167
+ /** Options for the layout fetch (`createLayoutContext`, or `layout` in `createPageContext`). */
168
+ export type LayoutOptions = {
169
+ /** Population depth for app, menus, labels, markets. Default `DEFAULT_LAYOUT_DEPTH` (1: app logo, menu media). */
170
+ depth?: number;
171
+ /** Default: the page draft option, or `'auto'` for `createLayoutContext`. */
172
+ draft?: DraftOption;
173
+ };
174
+
175
+ /** The `layout` option of `createPageContext`: layout options, or `false` to skip the layout. */
176
+ export type LayoutOption = LayoutOptions | false;
177
+
178
+ /** `ILayout`, or `undefined` when the layout option is `false`. */
179
+ export type LayoutFor<TLayout extends LayoutOption> = TLayout extends false ? undefined : ILayout;
180
+
181
+ export type LayoutContextOptions = LayoutOptions & {
182
+ config: Promise<SanitizedConfig>;
183
+ headers?: ReadonlyHeaders;
184
+ };
118
185
 
119
- /** A generated page collection document enriched with Bowl's page envelope. */
120
- export type TypedPage<TSlug extends CollectionSlug> = BowlDecorator<PageDocument<TSlug>> & IPage<ICategorized>;
186
+ export type PageContextOptions<
187
+ TSlug extends CollectionSlug = CollectionSlug,
188
+ TSelect extends PageSelect<TSlug> = undefined,
189
+ TLayout extends LayoutOption = LayoutOption,
190
+ > = {
191
+ config: Promise<SanitizedConfig>;
192
+ headers?: ReadonlyHeaders;
193
+ /**
194
+ * Population depth. Default `DEFAULT_PAGE_DEPTH` (2).
195
+ * 0 = ids only, 1 = direct relations/uploads (including those inside blocks),
196
+ * 2 = relations of relations (e.g. card covers).
197
+ */
198
+ depth?: number;
199
+ /** Payload select for the page document. Envelope fields are always merged in. */
200
+ select?: TSelect;
201
+ /** Per-collection select applied to populated documents, e.g. `{ media: { url: true } }`. */
202
+ populate?: PopulateType;
203
+ joins?: JoinQuery<TSlug> | false;
204
+ /** `true` / `false`, or `'auto'` to read Next `draftMode()`. Default `'auto'`. */
205
+ draft?: DraftOption;
206
+ /** Options for the layout fetch, or `false` to skip loading the layout (`layout` is then `undefined`). */
207
+ layout?: TLayout;
208
+ };
209
+
210
+ /** Read options shared by every local API call made for a page or layout. */
211
+ export type ReadOptions = {
212
+ depth?: number;
213
+ draft?: boolean;
214
+ select?: SelectType;
215
+ populate?: PopulateType;
216
+ joins?: JoinQuery<any> | false;
217
+ };
121
218
 
122
- export type TypedPageContext<TSlug extends CollectionSlug = CollectionSlug> = {
123
- layout: ILayout;
124
- page: TypedPage<TSlug> | undefined;
219
+ export type TypedPageContext<
220
+ TSlug extends CollectionSlug = CollectionSlug,
221
+ TSelect extends PageSelect<TSlug> = undefined,
222
+ TLayout extends LayoutOption = LayoutOptions,
223
+ > = {
224
+ /** `undefined` when the page context was created with `layout: false`. */
225
+ layout: LayoutFor<TLayout>;
226
+ page: TypedPage<TSlug, TSelect> | undefined;
125
227
  user: IUser | IEndUser | null;
126
228
  label: (key: string, params?: Record<string, any>) => string;
127
229
  link: (category: string) => string | undefined;