@delmaredigital/payload-experiments 0.3.0-rc.1 → 0.4.0-rc.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 (338) hide show
  1. package/README.md +172 -21
  2. package/dist/admin/MetricSourceSelect.js +3 -3
  3. package/dist/admin/PlacementStatus.d.ts +3 -0
  4. package/dist/admin/PlacementStatus.js +54 -0
  5. package/dist/admin/PreviewLinks.d.ts +2 -1
  6. package/dist/admin/PreviewLinks.js +47 -26
  7. package/dist/admin/TestsNavLink.d.ts +3 -1
  8. package/dist/admin/TestsNavLink.js +2 -2
  9. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  10. package/dist/admin/actions/ConfirmDialog.js +2 -2
  11. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  12. package/dist/admin/actions/DecideDialog.js +2 -2
  13. package/dist/admin/api.d.ts +11 -2
  14. package/dist/admin/api.js +20 -2
  15. package/dist/admin/armSelect/ArmSelectField.js +14 -22
  16. package/dist/admin/arms/arms.model.d.ts +1 -21
  17. package/dist/admin/arms/arms.model.js +1 -153
  18. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  19. package/dist/admin/blocks/BlockPanel.js +153 -0
  20. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  21. package/dist/admin/blocks/TestBlockField.js +163 -0
  22. package/dist/admin/blocks/formApi.d.ts +43 -0
  23. package/dist/admin/blocks/formApi.js +153 -0
  24. package/dist/admin/blocks/inject.d.ts +7 -0
  25. package/dist/admin/blocks/inject.js +41 -0
  26. package/dist/admin/blocks/model.d.ts +17 -0
  27. package/dist/admin/blocks/model.js +45 -0
  28. package/dist/admin/blocks/statusCache.d.ts +7 -0
  29. package/dist/admin/blocks/statusCache.js +83 -0
  30. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  31. package/dist/admin/blocks/useBlockForm.js +46 -0
  32. package/dist/admin/copy/actions.d.ts +2 -0
  33. package/dist/admin/copy/actions.js +9 -0
  34. package/dist/admin/copy/arms.d.ts +1 -6
  35. package/dist/admin/copy/arms.js +1 -16
  36. package/dist/admin/copy/blocks.d.ts +27 -0
  37. package/dist/admin/copy/blocks.js +38 -0
  38. package/dist/admin/copy/goals.d.ts +2 -0
  39. package/dist/admin/copy/goals.js +4 -1
  40. package/dist/admin/copy/newTest.d.ts +10 -0
  41. package/dist/admin/copy/newTest.js +10 -0
  42. package/dist/admin/copy/pages.d.ts +26 -0
  43. package/dist/admin/copy/pages.js +26 -0
  44. package/dist/admin/copy/readiness.d.ts +25 -3
  45. package/dist/admin/copy/readiness.js +25 -3
  46. package/dist/admin/copy/results.d.ts +3 -0
  47. package/dist/admin/copy/results.js +4 -1
  48. package/dist/admin/copy/review.d.ts +1 -1
  49. package/dist/admin/copy/review.js +1 -1
  50. package/dist/admin/copy/surface.d.ts +0 -1
  51. package/dist/admin/copy/surface.js +0 -1
  52. package/dist/admin/copy/testPage.d.ts +4 -0
  53. package/dist/admin/copy/testPage.js +4 -0
  54. package/dist/admin/goals/XpGoalField.d.ts +2 -0
  55. package/dist/admin/goals/XpGoalField.js +152 -0
  56. package/dist/admin/metricSource.model.d.ts +2 -0
  57. package/dist/admin/metricSource.model.js +4 -0
  58. package/dist/admin/pageCollections.d.ts +10 -0
  59. package/dist/admin/pageCollections.js +20 -0
  60. package/dist/admin/paths.d.ts +8 -1
  61. package/dist/admin/paths.js +8 -1
  62. package/dist/admin/placementStatus.model.d.ts +2 -0
  63. package/dist/admin/placementStatus.model.js +19 -0
  64. package/dist/admin/placements/placementLabels.d.ts +11 -0
  65. package/dist/admin/placements/placementLabels.js +21 -0
  66. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  67. package/dist/admin/placements/placementLabels.model.js +12 -0
  68. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  69. package/dist/admin/placements/usePlacementLabels.js +102 -0
  70. package/dist/admin/previewLinks.model.d.ts +2 -0
  71. package/dist/admin/previewLinks.model.js +3 -0
  72. package/dist/admin/results/DataQualityPanel.js +4 -1
  73. package/dist/admin/results/legacy.d.ts +16 -0
  74. package/dist/admin/results/legacy.js +27 -0
  75. package/dist/admin/results/results.model.d.ts +9 -6
  76. package/dist/admin/results/results.model.js +22 -9
  77. package/dist/admin/results/snapshot.d.ts +14 -0
  78. package/dist/admin/results/snapshot.js +3 -0
  79. package/dist/admin/surface/TestsHome.js +43 -15
  80. package/dist/admin/surface/TestsSurface.js +5 -1
  81. package/dist/admin/surface/contracts.d.ts +6 -5
  82. package/dist/admin/surface/goals/GoalForm.js +67 -34
  83. package/dist/admin/surface/goals/GoalsList.js +3 -3
  84. package/dist/admin/surface/goals/data.js +19 -16
  85. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  86. package/dist/admin/surface/goals/goals.model.js +1 -3
  87. package/dist/admin/surface/home.model.d.ts +2 -1
  88. package/dist/admin/surface/home.model.js +8 -4
  89. package/dist/admin/surface/newTest/NewTest.js +58 -17
  90. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  91. package/dist/admin/surface/newTest/PageWizard.js +209 -0
  92. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  93. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  94. package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
  95. package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
  96. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
  97. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  98. package/dist/admin/surface/readiness/ActionDialogs.js +103 -69
  99. package/dist/admin/surface/readiness/Banner.js +30 -6
  100. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  101. package/dist/admin/surface/readiness/readiness.model.js +95 -6
  102. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  103. package/dist/admin/surface/results/ReleasePages.js +3 -7
  104. package/dist/admin/surface/results/ResultsBody.js +23 -3
  105. package/dist/admin/surface/results/ResultsHeader.js +2 -5
  106. package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
  107. package/dist/admin/surface/results/recommendation.model.js +10 -3
  108. package/dist/admin/surface/results/useResults.d.ts +10 -1
  109. package/dist/admin/surface/surface.css +0 -1
  110. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  111. package/dist/admin/surface/test/GoalSection.js +97 -22
  112. package/dist/admin/surface/test/LengthSection.js +1 -1
  113. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  114. package/dist/admin/surface/test/PageHoldout.js +67 -0
  115. package/dist/admin/surface/test/TestPage.js +2 -1
  116. package/dist/admin/surface/test/VersionsSection.js +12 -5
  117. package/dist/admin/surface/test/fields.d.ts +13 -4
  118. package/dist/admin/surface/test/fields.js +46 -14
  119. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  120. package/dist/admin/surface/test/jumpToSection.js +4 -3
  121. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  122. package/dist/admin/surface/test/pageParts.js +79 -2
  123. package/dist/admin/surface/test/testPage.css +8 -1
  124. package/dist/admin/surface/test/testPage.model.d.ts +2 -3
  125. package/dist/admin/surface/test/testPage.model.js +1 -21
  126. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  127. package/dist/admin/surface/testDocument.model.js +2 -1
  128. package/dist/admin/surface/useTestDocument.d.ts +11 -2
  129. package/dist/admin/surface/useTestDocument.js +55 -16
  130. package/dist/admin/useApi.d.ts +11 -2
  131. package/dist/admin/useApi.js +3 -5
  132. package/dist/admin/useLayerUsage.js +14 -23
  133. package/dist/admin/views/ExperimentsRootView.js +58 -16
  134. package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
  135. package/dist/admin/views/TestsNavLinkServer.js +43 -0
  136. package/dist/arms/copy.d.ts +6 -0
  137. package/dist/arms/copy.js +16 -0
  138. package/dist/arms/model.d.ts +23 -0
  139. package/dist/arms/model.js +173 -0
  140. package/dist/blocks/bindings.d.ts +3 -0
  141. package/dist/blocks/bindings.js +34 -0
  142. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  143. package/dist/blocks/createExperimentBlock.js +77 -0
  144. package/dist/blocks/formState.d.ts +31 -0
  145. package/dist/blocks/formState.js +146 -0
  146. package/dist/blocks/model.d.ts +41 -0
  147. package/dist/blocks/model.js +34 -0
  148. package/dist/client/ExperimentGroup.d.ts +6 -0
  149. package/dist/client/ExperimentGroup.js +9 -0
  150. package/dist/client/ExperimentsProvider.js +20 -4
  151. package/dist/client/clickGoals.d.ts +4 -0
  152. package/dist/client/clickGoals.js +43 -0
  153. package/dist/collections/experiments/config.js +12 -2
  154. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  155. package/dist/collections/experiments/deleteGuard.js +12 -1
  156. package/dist/collections/experiments/editability.js +4 -2
  157. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  158. package/dist/collections/experiments/pickSlug.js +8 -0
  159. package/dist/collections/labels.d.ts +3 -0
  160. package/dist/collections/labels.js +20 -0
  161. package/dist/collections/metrics.js +12 -2
  162. package/dist/collections/phases.js +1 -0
  163. package/dist/collections/serialize.js +7 -0
  164. package/dist/db/appends.d.ts +6 -2
  165. package/dist/db/appends.js +10 -4
  166. package/dist/db/clickGoals.d.ts +9 -0
  167. package/dist/db/clickGoals.js +22 -0
  168. package/dist/db/delete.d.ts +1 -0
  169. package/dist/db/delete.js +1 -0
  170. package/dist/db/locks.d.ts +14 -0
  171. package/dist/db/locks.js +36 -1
  172. package/dist/db/names.d.ts +4 -0
  173. package/dist/db/names.js +4 -0
  174. package/dist/db/pageBindings.d.ts +4 -0
  175. package/dist/db/pageBindings.js +17 -0
  176. package/dist/db/pageBusy.d.ts +2 -0
  177. package/dist/db/pageBusy.js +10 -0
  178. package/dist/db/pageContent.d.ts +10 -0
  179. package/dist/db/pageContent.js +79 -0
  180. package/dist/db/pageOccupancy.d.ts +12 -0
  181. package/dist/db/pageOccupancy.js +45 -0
  182. package/dist/db/placements.d.ts +33 -0
  183. package/dist/db/placements.js +54 -0
  184. package/dist/db/slugs.d.ts +8 -0
  185. package/dist/db/slugs.js +40 -0
  186. package/dist/db/snapshots.d.ts +6 -0
  187. package/dist/db/snapshots.js +9 -5
  188. package/dist/endpoints/clickKeys.d.ts +5 -0
  189. package/dist/endpoints/clickKeys.js +17 -0
  190. package/dist/endpoints/events.js +4 -1
  191. package/dist/endpoints/index.js +28 -0
  192. package/dist/endpoints/pages.d.ts +5 -0
  193. package/dist/endpoints/pages.js +120 -0
  194. package/dist/endpoints/placementStatus.d.ts +6 -0
  195. package/dist/endpoints/placementStatus.js +238 -0
  196. package/dist/endpoints/readBody.d.ts +2 -0
  197. package/dist/endpoints/readBody.js +24 -0
  198. package/dist/endpoints/results.js +5 -1
  199. package/dist/endpoints/summary.js +11 -4
  200. package/dist/endpoints/wrapBlock.d.ts +6 -0
  201. package/dist/endpoints/wrapBlock.js +76 -0
  202. package/dist/errors.d.ts +3 -3
  203. package/dist/errors.js +4 -4
  204. package/dist/events/rows.d.ts +1 -1
  205. package/dist/events/rows.js +5 -3
  206. package/dist/exports/adminClient.d.ts +5 -0
  207. package/dist/exports/adminClient.js +5 -0
  208. package/dist/exports/adminRsc.d.ts +1 -0
  209. package/dist/exports/adminRsc.js +1 -0
  210. package/dist/exports/blocks.d.ts +2 -0
  211. package/dist/exports/blocks.js +1 -0
  212. package/dist/exports/client.d.ts +1 -0
  213. package/dist/exports/client.js +1 -0
  214. package/dist/exports/index.d.ts +6 -1
  215. package/dist/exports/index.js +3 -0
  216. package/dist/exports/puck.d.ts +5 -2
  217. package/dist/exports/puck.js +3 -1
  218. package/dist/exports/puckEditor.d.ts +6 -1
  219. package/dist/exports/puckEditor.js +4 -1
  220. package/dist/exports/rsc.d.ts +4 -0
  221. package/dist/exports/rsc.js +2 -0
  222. package/dist/fields/xpGoal.d.ts +3 -0
  223. package/dist/fields/xpGoal.js +28 -0
  224. package/dist/globals/state.js +10 -0
  225. package/dist/goals/cache.d.ts +3 -0
  226. package/dist/goals/cache.js +35 -0
  227. package/dist/goals/fairness.d.ts +40 -0
  228. package/dist/goals/fairness.js +200 -0
  229. package/dist/goals/key.d.ts +4 -0
  230. package/dist/goals/key.js +3 -0
  231. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  232. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  233. package/dist/goals/picker/copy.d.ts +16 -0
  234. package/dist/goals/picker/copy.js +15 -0
  235. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  236. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  237. package/dist/hooks/pageGuard.d.ts +5 -1
  238. package/dist/hooks/pageGuard.js +160 -80
  239. package/dist/lib/fetchAllDocs.d.ts +16 -0
  240. package/dist/lib/fetchAllDocs.js +56 -0
  241. package/dist/onInit.js +29 -0
  242. package/dist/options.d.ts +2 -0
  243. package/dist/options.js +43 -10
  244. package/dist/plugin.js +45 -28
  245. package/dist/puck/ExperimentPicker.js +47 -16
  246. package/dist/puck/ExperimentSlot.d.ts +51 -1
  247. package/dist/puck/ExperimentSlot.js +23 -12
  248. package/dist/puck/copy.d.ts +6 -4
  249. package/dist/puck/copy.js +6 -4
  250. package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
  251. package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
  252. package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
  253. package/dist/puck/editor/ResultsReadout.d.ts +6 -0
  254. package/dist/puck/editor/ResultsReadout.js +99 -0
  255. package/dist/puck/editor/VersionBuilder.d.ts +34 -0
  256. package/dist/puck/editor/VersionBuilder.js +317 -0
  257. package/dist/puck/editor/armWrites.d.ts +7 -0
  258. package/dist/puck/editor/armWrites.js +71 -0
  259. package/dist/puck/editor/armsCache.d.ts +14 -0
  260. package/dist/puck/editor/armsCache.js +78 -0
  261. package/dist/puck/editor/copy.d.ts +33 -1
  262. package/dist/puck/editor/copy.js +45 -8
  263. package/dist/puck/editor/createTest.d.ts +1 -1
  264. package/dist/puck/editor/createTest.js +25 -14
  265. package/dist/puck/editor/editor.chrome.css +48 -161
  266. package/dist/puck/editor/editor.css +48 -161
  267. package/dist/puck/editor/editor.model.d.ts +8 -3
  268. package/dist/puck/editor/editor.model.js +16 -10
  269. package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
  270. package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
  271. package/dist/puck/editor/integration.d.ts +10 -0
  272. package/dist/puck/editor/integration.js +30 -0
  273. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  274. package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
  275. package/dist/puck/experimentSlotField.d.ts +2 -0
  276. package/dist/puck/experimentSlotField.js +11 -3
  277. package/dist/puck/hostContext.d.ts +10 -0
  278. package/dist/puck/hostContext.js +12 -0
  279. package/dist/puck/integration.d.ts +6 -0
  280. package/dist/puck/integration.js +21 -0
  281. package/dist/puck/routes.d.ts +12 -0
  282. package/dist/puck/routes.js +11 -0
  283. package/dist/puck/slotConfig.d.ts +1 -1
  284. package/dist/puck/slotConfig.js +2 -2
  285. package/dist/puck/xpGoalField.d.ts +3 -0
  286. package/dist/puck/xpGoalField.js +6 -0
  287. package/dist/results/health.d.ts +4 -0
  288. package/dist/results/health.js +27 -0
  289. package/dist/results/snapshot.js +15 -2
  290. package/dist/sanitizeReadinessDetail.js +51 -1
  291. package/dist/server/metadata.js +9 -2
  292. package/dist/server/normaliseContentRelationships.d.ts +7 -0
  293. package/dist/server/normaliseContentRelationships.js +62 -0
  294. package/dist/server/pageKindSeoContract.d.ts +27 -0
  295. package/dist/server/pageKindSeoContract.js +80 -0
  296. package/dist/server/recordContentChange.d.ts +1 -1
  297. package/dist/server/recordContentChange.js +72 -13
  298. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  299. package/dist/server/resolveExperimentBlocks.js +54 -0
  300. package/dist/server/runResults.js +92 -19
  301. package/dist/server/toClientAssignments.js +2 -1
  302. package/dist/transitions/addMetric.d.ts +2 -1
  303. package/dist/transitions/addMetric.js +28 -1
  304. package/dist/transitions/checkStart.d.ts +17 -1
  305. package/dist/transitions/checkStart.js +99 -7
  306. package/dist/transitions/decide.js +40 -1
  307. package/dist/transitions/deleteExperiment.d.ts +3 -1
  308. package/dist/transitions/deleteExperiment.js +27 -1
  309. package/dist/transitions/kernel.js +4 -0
  310. package/dist/transitions/makeContent.d.ts +5 -0
  311. package/dist/transitions/makeContent.js +39 -11
  312. package/dist/transitions/pageCollection.d.ts +10 -0
  313. package/dist/transitions/pageCollection.js +20 -0
  314. package/dist/transitions/pageVersions.d.ts +10 -0
  315. package/dist/transitions/pageVersions.js +44 -0
  316. package/dist/transitions/phaseRecord.d.ts +1 -0
  317. package/dist/transitions/placementInteractions.d.ts +40 -0
  318. package/dist/transitions/placementInteractions.js +94 -0
  319. package/dist/transitions/placements.d.ts +10 -6
  320. package/dist/transitions/placements.js +156 -2
  321. package/dist/transitions/preflightData.d.ts +8 -0
  322. package/dist/transitions/preflightData.js +248 -36
  323. package/dist/transitions/resolveCaps.d.ts +1 -0
  324. package/dist/transitions/resolveCaps.js +6 -2
  325. package/dist/transitions/start.js +77 -3
  326. package/dist/types/admin.d.ts +1 -1
  327. package/dist/types/assignments.d.ts +1 -0
  328. package/dist/types/contracts.d.ts +31 -10
  329. package/dist/types/endpoints.d.ts +92 -5
  330. package/dist/types/index.d.ts +3 -0
  331. package/dist/types/puck.d.ts +7 -1
  332. package/dist/types/snapshot.d.ts +17 -4
  333. package/dist/types/surface.d.ts +13 -0
  334. package/dist/ui/Tabs.d.ts +4 -2
  335. package/dist/ui/Tabs.js +33 -26
  336. package/dist/ui/accent.d.ts +22 -3
  337. package/dist/ui/accent.js +74 -16
  338. package/package.json +12 -2
@@ -0,0 +1,146 @@
1
+ import { BLOCK_SLOTS, EXPERIMENT_BLOCK_SLUG, isObject, slotBlocks } from './model.js';
2
+ /** Mirror Payload 3.90.2's collections/operations/utilities/copyDataWithFreshRowIDs.js:
3
+ * walk configured row/subfield containers only. Mint ids for array/blocks rows, leaving
4
+ * opaque values (including json, richText, relationships and uploads) unchanged.
5
+ * Global block definitions resolve blockReferences; inline slot blocks retain host configs.
6
+ */ export function freshBlockIds(value, field, config = {}, mint = ()=>globalThis.crypto.randomUUID()) {
7
+ function copyObject(data, fields, parentIsLocalized = false) {
8
+ const result = {
9
+ ...data
10
+ };
11
+ for (const child of fields){
12
+ if (child.type === 'row' || child.type === 'collapsible' || child.type === 'group' && !('name' in child && child.name)) {
13
+ Object.assign(result, copyObject(result, child.fields, parentIsLocalized));
14
+ } else if (child.type === 'tabs') {
15
+ for (const tab of child.tabs){
16
+ if ('name' in tab && tab.name) {
17
+ if (Object.hasOwn(result, tab.name)) result[tab.name] = subfields(result[tab.name], tab.fields, tab.localized, parentIsLocalized);
18
+ } else Object.assign(result, copyObject(result, tab.fields, parentIsLocalized));
19
+ }
20
+ } else if (child.type === 'group' && 'name' in child && child.name) {
21
+ if (Object.hasOwn(result, child.name)) result[child.name] = subfields(result[child.name], child.fields, child.localized, parentIsLocalized);
22
+ } else if (child.type === 'array' || child.type === 'blocks') {
23
+ if (Object.hasOwn(result, child.name)) result[child.name] = copyRows(result[child.name], child, parentIsLocalized || Boolean(child.localized));
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ function subfields(data, fields, localized, parentIsLocalized) {
29
+ if (!isObject(data)) return data;
30
+ const locales = config.localization ? config.localization.localeCodes : [];
31
+ if (localized && !parentIsLocalized && Object.keys(data).length > 0 && Object.keys(data).every((key)=>locales.includes(key))) return Object.fromEntries(Object.entries(data).map(([locale, value])=>[
32
+ locale,
33
+ isObject(value) ? copyObject(value, fields, true) : value
34
+ ]));
35
+ return copyObject(data, fields, parentIsLocalized || Boolean(localized));
36
+ }
37
+ function copyRows(data, rowsField, parentIsLocalized = false) {
38
+ if (Array.isArray(data)) return data.map((row)=>{
39
+ if (!isObject(row)) return row;
40
+ const fields = rowsField.type === 'array' ? rowsField.fields : (config.blocks?.find((block)=>block.slug === row.blockType) ?? [
41
+ ...rowsField.blocks,
42
+ ...(rowsField.blockReferences ?? []).filter((block)=>typeof block !== 'string')
43
+ ].find((block)=>block.slug === row.blockType))?.fields ?? [];
44
+ return {
45
+ ...copyObject(row, fields, parentIsLocalized),
46
+ id: mint()
47
+ };
48
+ });
49
+ if (isObject(data)) return Object.fromEntries(Object.entries(data).map(([locale, rows])=>[
50
+ locale,
51
+ Array.isArray(rows) ? copyRows(rows, rowsField, true) : rows
52
+ ]));
53
+ return data;
54
+ }
55
+ return copyRows(value, field);
56
+ }
57
+ export function wrapExperimentBlock(block, experiment, field, config = {}, blockSlug = EXPERIMENT_BLOCK_SLUG) {
58
+ if (block.blockType === blockSlug) throw new Error('Experiment blocks cannot be nested.');
59
+ return {
60
+ id: globalThis.crypto.randomUUID(),
61
+ blockType: blockSlug,
62
+ experiment,
63
+ slots: [
64
+ {
65
+ id: globalThis.crypto.randomUUID(),
66
+ slot: 'control',
67
+ blocks: [
68
+ block
69
+ ]
70
+ },
71
+ {
72
+ id: globalThis.crypto.randomUUID(),
73
+ slot: 'variantA',
74
+ blocks: freshBlockIds([
75
+ block
76
+ ], field, config)
77
+ }
78
+ ]
79
+ };
80
+ }
81
+ function copySlotBlocks(block, slot, field, config) {
82
+ // Traverse the wrapper too: slot content may use blocks defined only inside
83
+ // createExperimentBlock, rather than in the host's outer blocks field.
84
+ return slotBlocks(freshBlockIds([
85
+ block
86
+ ], field, config)[0], slot);
87
+ }
88
+ export function syncExperimentSlots(block, arms, field, config = {}) {
89
+ const wanted = arms.filter((a)=>a.kind !== 'holdout' && a.key !== 'holdout').map((a)=>a.slot ?? a.key).filter((slot)=>BLOCK_SLOTS.includes(slot));
90
+ const slots = [
91
+ ...new Set(wanted)
92
+ ].map((slot)=>block.slots.find((row)=>row.slot === slot) ?? {
93
+ id: globalThis.crypto.randomUUID(),
94
+ slot,
95
+ blocks: copySlotBlocks(block, 'control', field, config)
96
+ });
97
+ return {
98
+ ...block,
99
+ slots
100
+ };
101
+ }
102
+ export function copyOriginalIntoVersion(block, slot, field, config = {}) {
103
+ return {
104
+ ...block,
105
+ slots: block.slots.map((row)=>row.slot === slot && slot !== 'control' ? {
106
+ ...row,
107
+ blocks: copySlotBlocks(block, 'control', field, config)
108
+ } : row)
109
+ };
110
+ }
111
+ export function unwrapExperimentBlock(block, field, slot = 'control', config = {}) {
112
+ return copySlotBlocks(block, slot, field, config);
113
+ }
114
+ /** Config coordinates include unnamed layout containers. No field component is injected here. */ export function findRegisteredBlocksField(fields, name) {
115
+ function visit(fields, path) {
116
+ for(let i = 0; i < fields.length; i++){
117
+ const field = fields[i], here = [
118
+ ...path,
119
+ i
120
+ ];
121
+ if ('name' in field && field.name === name && field.type === 'blocks') return {
122
+ field,
123
+ path: here
124
+ };
125
+ if (field.type === 'tabs') for(let t = 0; t < field.tabs.length; t++){
126
+ const tab = field.tabs[t];
127
+ if ('name' in tab && tab.name) continue;
128
+ const found = visit(field.tabs[t].fields, [
129
+ ...here,
130
+ 'tabs',
131
+ t,
132
+ 'fields'
133
+ ]);
134
+ if (found) return found;
135
+ }
136
+ if (field.type === 'row' || field.type === 'collapsible') {
137
+ const found = visit(field.fields, [
138
+ ...here,
139
+ 'fields'
140
+ ]);
141
+ if (found) return found;
142
+ }
143
+ }
144
+ }
145
+ return visit(fields, []);
146
+ }
@@ -0,0 +1,41 @@
1
+ import type { RenderedExposure } from '../types/assignments.js';
2
+ export declare const EXPERIMENT_BLOCK_SLUG = "xpExperiment";
3
+ export declare const BLOCK_SLOTS: readonly ["control", "variantA", "variantB", "variantC"];
4
+ export interface HostBlock {
5
+ id?: string;
6
+ blockType: string;
7
+ blockName?: string;
8
+ [field: string]: unknown;
9
+ }
10
+ export interface ExperimentBlock extends HostBlock {
11
+ experiment: string;
12
+ slots: Array<{
13
+ id?: string;
14
+ slot: string;
15
+ blocks: HostBlock[];
16
+ }>;
17
+ }
18
+ export interface ExperimentBlockGroup {
19
+ slug: string;
20
+ expose?: RenderedExposure;
21
+ }
22
+ export type ResolvedHostBlock = HostBlock & {
23
+ xpGroup?: ExperimentBlockGroup;
24
+ };
25
+ export interface ResolvedExperimentBlocks {
26
+ blocks: ResolvedHostBlock[];
27
+ rendered: RenderedExposure[];
28
+ groups: Array<ExperimentBlockGroup & {
29
+ start: number;
30
+ end: number;
31
+ }>;
32
+ }
33
+ export declare const isObject: (v: unknown) => v is Record<string, unknown>;
34
+ export declare const hostBlocks: (v: unknown) => HostBlock[];
35
+ export declare function slotBlocks(block: HostBlock, slot: string): HostBlock[];
36
+ /** Experiments-off rendering preserves stored row ids; only top-level wrappers are read. */
37
+ export declare function controlOnlyBlocks(blocks: unknown, blockSlug?: string): HostBlock[];
38
+ export declare function segmentExperimentBlocks(blocks: readonly ResolvedHostBlock[]): Array<{
39
+ group?: ExperimentBlockGroup;
40
+ blocks: HostBlock[];
41
+ }>;
@@ -0,0 +1,34 @@
1
+ export const EXPERIMENT_BLOCK_SLUG = 'xpExperiment';
2
+ export const BLOCK_SLOTS = [
3
+ 'control',
4
+ 'variantA',
5
+ 'variantB',
6
+ 'variantC'
7
+ ];
8
+ export const isObject = (v)=>!!v && typeof v === 'object' && !Array.isArray(v);
9
+ export const hostBlocks = (v)=>Array.isArray(v) ? v.filter((b)=>isObject(b) && typeof b.blockType === 'string') : [];
10
+ export function slotBlocks(block, slot) {
11
+ const row = Array.isArray(block.slots) ? block.slots.find((row)=>isObject(row) && row.slot === slot) : undefined;
12
+ return isObject(row) ? hostBlocks(row.blocks) : [];
13
+ }
14
+ /** Experiments-off rendering preserves stored row ids; only top-level wrappers are read. */ export function controlOnlyBlocks(blocks, blockSlug = EXPERIMENT_BLOCK_SLUG) {
15
+ return hostBlocks(blocks).flatMap((block)=>block.blockType === blockSlug ? slotBlocks(block, 'control') : [
16
+ block
17
+ ]);
18
+ }
19
+ export function segmentExperimentBlocks(blocks) {
20
+ const runs = [];
21
+ for (const block of blocks){
22
+ const group = block.xpGroup, last = runs.at(-1);
23
+ if (last && last.group === group) last.blocks.push(block);
24
+ else runs.push({
25
+ ...group ? {
26
+ group
27
+ } : {},
28
+ blocks: [
29
+ block
30
+ ]
31
+ });
32
+ }
33
+ return runs;
34
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { RenderedExposure } from '../types/assignments.js';
3
+ export declare function ExperimentGroup({ expose, children }: {
4
+ expose?: RenderedExposure;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ViewportExposure } from './ViewportExposure.js';
4
+ export function ExperimentGroup({ expose, children }) {
5
+ return /*#__PURE__*/ _jsx(ViewportExposure, {
6
+ expose: expose,
7
+ children: children
8
+ });
9
+ }
@@ -1,9 +1,12 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useLayoutEffect, useRef } from 'react';
3
+ import { createContext, useContext, useLayoutEffect, useRef } from 'react';
4
4
  import { registerApiRoute, unregisterApiRoute } from './beacon.js';
5
+ import { registerClickGoals, unregisterClickGoals } from './clickGoals.js';
5
6
  import { ExperimentsContext } from './context.js';
7
+ const DepthContext = /*#__PURE__*/ createContext(0);
6
8
  export function ExperimentsProvider(props) {
9
+ const depth = useContext(DepthContext) + 1;
7
10
  const token = useRef(Symbol()).current;
8
11
  // Register committed providers before descendants can send from passive effects.
9
12
  useLayoutEffect(()=>{
@@ -15,8 +18,21 @@ export function ExperimentsProvider(props) {
15
18
  useLayoutEffect(()=>()=>unregisterApiRoute(token), [
16
19
  token
17
20
  ]);
18
- return /*#__PURE__*/ _jsx(ExperimentsContext.Provider, {
19
- value: props.value,
20
- children: props.children
21
+ useLayoutEffect(()=>{
22
+ registerClickGoals(token, depth, props.value);
23
+ }, [
24
+ token,
25
+ depth,
26
+ props.value
27
+ ]);
28
+ useLayoutEffect(()=>()=>unregisterClickGoals(token), [
29
+ token
30
+ ]);
31
+ return /*#__PURE__*/ _jsx(DepthContext.Provider, {
32
+ value: depth,
33
+ children: /*#__PURE__*/ _jsx(ExperimentsContext.Provider, {
34
+ value: props.value,
35
+ children: props.children
36
+ })
21
37
  });
22
38
  }
@@ -0,0 +1,4 @@
1
+ import type { ClientAssignments } from '../types/assignments.js';
2
+ /** One delegated listener; depth, rather than React's child-first commit order, decides ownership. */
3
+ export declare function registerClickGoals(token: symbol, depth: number, value: ClientAssignments): void;
4
+ export declare function unregisterClickGoals(token: symbol): void;
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { CLICK_KEY } from '../goals/key.js';
3
+ import { sendEvent } from './beacon.js';
4
+ import { idempotencyKey } from './idempotency.js';
5
+ const providers = new Map();
6
+ let listening;
7
+ function onClick(event) {
8
+ let owner;
9
+ for (const provider of providers.values()){
10
+ if (provider.value.clickGoalsArmed && (!owner || provider.depth >= owner.depth)) owner = provider;
11
+ }
12
+ if (!owner) return;
13
+ const target = event.target;
14
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
15
+ const key = element?.closest('[data-xp-goal]')?.getAttribute('data-xp-goal');
16
+ if (!key || !CLICK_KEY.test(key)) return;
17
+ sendEvent(owner.value.apiRoute, {
18
+ type: 'conversion',
19
+ eventName: `click:${key}`,
20
+ idempotencyKey: `${owner.pageviewId}:${key}`
21
+ });
22
+ }
23
+ /** One delegated listener; depth, rather than React's child-first commit order, decides ownership. */ export function registerClickGoals(token, depth, value) {
24
+ const previous = providers.get(token);
25
+ const pageviewId = previous?.value === value ? previous.pageviewId : idempotencyKey();
26
+ providers.delete(token);
27
+ providers.set(token, {
28
+ depth,
29
+ value,
30
+ pageviewId
31
+ });
32
+ if (!listening && typeof document !== 'undefined') {
33
+ listening = document;
34
+ listening.addEventListener('click', onClick);
35
+ }
36
+ }
37
+ export function unregisterClickGoals(token) {
38
+ providers.delete(token);
39
+ if (!providers.size && listening) {
40
+ listening.removeEventListener('click', onClick);
41
+ listening = undefined;
42
+ }
43
+ }
@@ -5,8 +5,9 @@ import { TARGETING_OPS, isOpValid } from '../../targeting/ops.js';
5
5
  import { readListValue } from '../../targeting/values.js';
6
6
  import { createDefaults } from './createDefaults.js';
7
7
  import { flagControlParams } from './flagControlParams.js';
8
+ import { pickExperimentSlug } from './pickSlug.js';
8
9
  import { lockHook } from './lockHook.js';
9
- import { deleteGuard, deleteGuardAfter } from './deleteGuard.js';
10
+ import { deleteGuard, deleteGuardAfter, deleteGuardBeforeOperation } from './deleteGuard.js';
10
11
  import { checkArms } from './armInvariants.js';
11
12
  import { XpNotImplemented, XpRefusal } from '../../errors.js';
12
13
  const targetingValue = (rule)=>rule.op === 'in' || rule.op === 'nin' ? readListValue(rule.value) : rule.value;
@@ -81,12 +82,16 @@ export function experimentsCollection(options) {
81
82
  }
82
83
  },
83
84
  hooks: {
85
+ beforeOperation: [
86
+ deleteGuardBeforeOperation
87
+ ],
84
88
  beforeValidate: [
85
89
  createDefaults
86
90
  ],
87
91
  beforeChange: [
88
92
  flagControlParams,
89
- lockHook
93
+ lockHook,
94
+ pickExperimentSlug
90
95
  ],
91
96
  beforeDelete: [
92
97
  deleteGuard
@@ -166,6 +171,11 @@ export function experimentsCollection(options) {
166
171
  read: async (args)=>args.doc?.kind !== 'code' || adminOnly(args)
167
172
  }
168
173
  },
174
+ {
175
+ name: 'pageCollection',
176
+ type: 'text',
177
+ validate: (value, { previousValue })=>value === previousValue || value == null || value === '' || options.pages?.collections.some((entry)=>entry.slug === value) || 'Choose a registered page collection.'
178
+ },
169
179
  {
170
180
  name: 'flagPoint',
171
181
  type: 'text',
@@ -1,3 +1,4 @@
1
- import type { CollectionAfterDeleteHook, CollectionBeforeDeleteHook } from 'payload';
1
+ import type { CollectionBeforeOperationHook, CollectionAfterDeleteHook, CollectionBeforeDeleteHook } from 'payload';
2
2
  export declare const deleteGuard: CollectionBeforeDeleteHook;
3
3
  export declare const deleteGuardAfter: CollectionAfterDeleteHook;
4
+ export declare const deleteGuardBeforeOperation: CollectionBeforeOperationHook;
@@ -1,4 +1,5 @@
1
- import { deleteExperiment, deleteExperimentAfter } from '../../transitions/deleteExperiment.js';
1
+ import { deleteExperiment, deleteExperimentAfter, prepareBulkDelete } from '../../transitions/deleteExperiment.js';
2
+ import { xpOptionsFromReq } from '../../options/fromReq.js';
2
3
  export const deleteGuard = async ({ req, id })=>{
3
4
  await deleteExperiment(req, id);
4
5
  };
@@ -6,3 +7,13 @@ export const deleteGuardAfter = async ({ req, id, doc })=>{
6
7
  await deleteExperimentAfter(req, id);
7
8
  return doc;
8
9
  };
10
+ export const deleteGuardBeforeOperation = async ({ operation, args, req })=>{
11
+ if (operation === 'delete' && 'where' in args && args.where) {
12
+ // Payload runs beforeOperation before access; only a manager, or an explicit overrideAccess call, may make the plugin discover and lock the delete scope.
13
+ if (args.overrideAccess !== true && await xpOptionsFromReq(req).access.manage({
14
+ req
15
+ }) !== true) return args;
16
+ await prepareBulkDelete(req, args.where);
17
+ }
18
+ return args;
19
+ };
@@ -8,7 +8,8 @@ export const EDITABLE_BY_STATUS = {
8
8
  'flagPoint',
9
9
  'metrics',
10
10
  'analysis',
11
- 'exposurePolicy'
11
+ 'exposurePolicy',
12
+ 'pageCollection'
12
13
  ],
13
14
  stopped: [
14
15
  'name',
@@ -18,7 +19,8 @@ export const EDITABLE_BY_STATUS = {
18
19
  'allocation',
19
20
  'metrics',
20
21
  'analysis',
21
- 'exposurePolicy'
22
+ 'exposurePolicy',
23
+ 'pageCollection'
22
24
  ],
23
25
  running: [
24
26
  'name',
@@ -0,0 +1,2 @@
1
+ import type { CollectionBeforeChangeHook } from 'payload';
2
+ export declare const pickExperimentSlug: CollectionBeforeChangeHook;
@@ -0,0 +1,8 @@
1
+ import { pickSlug } from '../../db/slugs.js';
2
+ import { pooledExecutor, txExecutor } from '../../db/executor.js';
3
+ export const pickExperimentSlug = async ({ req, data, operation })=>{
4
+ if (operation !== 'create' || typeof data.slug !== 'string' || !data.slug) return data;
5
+ const transactional = await req.transactionID != null;
6
+ data.slug = await pickSlug(transactional ? txExecutor(req) : pooledExecutor(req.payload), data.slug, transactional);
7
+ return data;
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ /** Payload labels may be translated strings or functions. Keep server entries dependency-safe. */
3
+ export declare function collectionLabel(label: unknown, slug: string, req: PayloadRequest): string;
@@ -0,0 +1,20 @@
1
+ /** Payload labels may be translated strings or functions. Keep server entries dependency-safe. */ export function collectionLabel(label, slug, req) {
2
+ try {
3
+ if (typeof label === 'function') {
4
+ const value = label({
5
+ i18n: req.i18n,
6
+ t: req.i18n.t
7
+ });
8
+ return typeof value === 'string' ? value : slug;
9
+ }
10
+ if (typeof label === 'string') return label;
11
+ const translations = label && typeof label === 'object' ? label : {};
12
+ return [
13
+ translations[req.i18n?.language ?? 'en'],
14
+ translations.en,
15
+ ...Object.values(translations)
16
+ ].find((value)=>typeof value === 'string') ?? slug;
17
+ } catch {
18
+ return slug;
19
+ }
20
+ }
@@ -1,3 +1,4 @@
1
+ import { CLICK_KEY, isClickSource, validClickSource } from '../goals/key.js';
1
2
  import { XP_METRIC_SOURCE_SELECT } from '../admin/paths.js';
2
3
  import { managedAccess } from './access.js';
3
4
  import { serializeMetricSources } from './serialize.js';
@@ -30,6 +31,14 @@ export function metricsCollection(options) {
30
31
  beforeValidate: [
31
32
  ({ data, originalDoc })=>{
32
33
  if (!data) return data;
34
+ if (isClickSource(data.source ?? originalDoc?.source)) {
35
+ data.source = `click:${data.slug ?? originalDoc?.slug ?? ''}`;
36
+ data.direction = 'increase';
37
+ data.capping = {
38
+ type: null,
39
+ value: null
40
+ };
41
+ }
33
42
  const source = sources.find((source)=>source.id === (data.source ?? originalDoc?.source));
34
43
  const capping = data.capping === undefined ? originalDoc?.capping : data.capping;
35
44
  // Payload may submit an empty optional group as { type: null, value: null }.
@@ -58,6 +67,7 @@ export function metricsCollection(options) {
58
67
  type: 'text',
59
68
  required: true,
60
69
  unique: true,
70
+ validate: (value, { data })=>!isClickSource(data?.source) || typeof value === 'string' && CLICK_KEY.test(value) || 'Use 1–64 lowercase letters, numbers, underscores or hyphens, starting with a letter or number.',
61
71
  admin: {
62
72
  description: 'Use a unique short name for code that refers to this goal.'
63
73
  }
@@ -67,7 +77,7 @@ export function metricsCollection(options) {
67
77
  label: 'Measured by',
68
78
  type: 'text',
69
79
  required: true,
70
- validate: (value)=>sources.some((source)=>source.id === value) || 'Choose what this goal is measured by.',
80
+ validate: (value)=>(isClickSource(value) ? validClickSource(value) : sources.some((source)=>source.id === value)) || 'Choose what this goal is measured by.',
71
81
  admin: {
72
82
  description: 'Choose the outcome this goal measures.',
73
83
  components: {
@@ -135,7 +145,7 @@ export function metricsCollection(options) {
135
145
  },
136
146
  validate: (value, { data, siblingData })=>{
137
147
  const source = sources.find((source)=>source.id === data?.source);
138
- return validateCappingType(value, siblingData?.value, source?.kind);
148
+ return validateCappingType(value, siblingData?.value, isClickSource(data?.source) ? 'binomial' : source?.kind);
139
149
  }
140
150
  },
141
151
  {
@@ -50,6 +50,7 @@ export function phasesCollection(options) {
50
50
  'targeting',
51
51
  'metricDefinitions',
52
52
  'analysis',
53
+ 'placementDocs',
53
54
  'contentChanges'
54
55
  ].map((name)=>({
55
56
  name,
@@ -12,6 +12,13 @@ export function serializeFlagPoints(options) {
12
12
  }
13
13
  export function serializeMetricSources(options) {
14
14
  return [
15
+ {
16
+ id: 'click:',
17
+ label: 'A click on the page',
18
+ kind: 'binomial',
19
+ keyedBy: 'visitor',
20
+ clientWritable: true
21
+ },
15
22
  ...(options.metricSources ?? []).map(({ id, label, kind, keyedBy, clientWritable })=>({
16
23
  id,
17
24
  label,
@@ -19,9 +19,13 @@ type AppendArgs<T> = {
19
19
  phase: number;
20
20
  entry: T;
21
21
  };
22
+ /** Raw main-row read on the host transaction, without afterRead expansion.
23
+ * Adapter reads avoid Local API error handling killing the host transaction
24
+ * before the audit savepoint can recover.
25
+ */
26
+ export declare function storedContent(req: PayloadRequest, collection: string, id: DocId): Promise<Record<string, unknown> | undefined>;
22
27
  /** Read the main collection row BEFORE the host write. Draft version retention
23
- * and unpublishing cannot remove this baseline. Adapter reads avoid Local API
24
- * error handling killing the host transaction before savepoint recovery.
28
+ * and unpublishing cannot remove this baseline.
25
29
  */
26
30
  export declare function previousPublishedContent(req: PayloadRequest, collection: string, id: DocId): Promise<Record<string, unknown> | undefined>;
27
31
  /** Serialize flagDefaults read/modify/write before the manifest builder bumps. */
@@ -1,10 +1,10 @@
1
1
  import { COLUMNS, TABLES } from './names.js';
2
2
  import { XpRefusal } from '../errors.js';
3
3
  import { qualifiedTable, quoteIdentifier as quote } from './sql.js';
4
- /** Read the main collection row BEFORE the host write. Draft version retention
5
- * and unpublishing cannot remove this baseline. Adapter reads avoid Local API
6
- * error handling killing the host transaction before savepoint recovery.
7
- */ export async function previousPublishedContent(req, collection, id) {
4
+ /** Raw main-row read on the host transaction, without afterRead expansion.
5
+ * Adapter reads avoid Local API error handling killing the host transaction
6
+ * before the audit savepoint can recover.
7
+ */ export async function storedContent(req, collection, id) {
8
8
  const doc = await req.payload.db.findOne({
9
9
  collection,
10
10
  req,
@@ -16,6 +16,12 @@ import { qualifiedTable, quoteIdentifier as quote } from './sql.js';
16
16
  // Adapter reads have no access layer; document the audit's access posture.
17
17
  overrideAccess: false
18
18
  });
19
+ return doc ?? undefined;
20
+ }
21
+ /** Read the main collection row BEFORE the host write. Draft version retention
22
+ * and unpublishing cannot remove this baseline.
23
+ */ export async function previousPublishedContent(req, collection, id) {
24
+ const doc = await storedContent(req, collection, id);
19
25
  if (!doc) return undefined;
20
26
  // A draft created with content is already in the main table. Only the
21
27
  // existence of a published version matters here; never use its content as
@@ -0,0 +1,9 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ import type { Executor } from './executor.js';
4
+ /** Defined click goals only; called on the pool by the public events endpoint. */
5
+ export declare function definedClickKeys(exec: Executor): Promise<Set<string>>;
6
+ /** §7.8: click-goal fairness reads published content raw. Never read draft versions. */
7
+ export declare function clickGoalContent(req: PayloadRequest, collection: string, id: DocId): Promise<(Record<string, unknown> & {
8
+ id: DocId;
9
+ }) | undefined>;
@@ -0,0 +1,22 @@
1
+ import { COLUMNS, TABLES } from './names.js';
2
+ import { qualifiedTable, quoteIdentifier as q } from './sql.js';
3
+ /** Defined click goals only; called on the pool by the public events endpoint. */ export async function definedClickKeys(exec) {
4
+ const c = COLUMNS.metrics;
5
+ const { rows } = await exec.query(`SELECT ${q(c.slug)} AS slug FROM ${qualifiedTable(exec, TABLES.metrics)} WHERE ${q(c.source)} = 'click:' || ${q(c.slug)}`);
6
+ return new Set(rows.map((row)=>row.slug));
7
+ }
8
+ /** §7.8: click-goal fairness reads published content raw. Never read draft versions. */ export async function clickGoalContent(req, collection, id) {
9
+ const doc = await req.payload.db.findOne({
10
+ collection,
11
+ req,
12
+ where: {
13
+ id: {
14
+ equals: id
15
+ }
16
+ },
17
+ // Adapter read: click-goal fairness reads published content raw.
18
+ overrideAccess: true
19
+ });
20
+ const drafts = req.payload.config.collections?.find((c)=>c.slug === collection)?.versions;
21
+ return doc && (!(drafts && drafts.drafts) || doc._status === 'published') ? doc : undefined;
22
+ }
@@ -3,6 +3,7 @@ import type { DocId } from '../types/contracts.js';
3
3
  import type { Executor } from './executor.js';
4
4
  interface DeleteState {
5
5
  tail: Promise<void>;
6
+ retiredSlugs: Set<string>;
6
7
  excludedIds: Map<string, DocId>;
7
8
  pruneFlagDefaultIds: Map<string, DocId>;
8
9
  }
package/dist/db/delete.js CHANGED
@@ -16,6 +16,7 @@ const transactions = new WeakMap();
16
16
  if (!state) {
17
17
  state = {
18
18
  tail: Promise.resolve(),
19
+ retiredSlugs: new Set(),
19
20
  excludedIds: new Map(),
20
21
  pruneFlagDefaultIds: new Map()
21
22
  };