@delmaredigital/payload-experiments 0.0.0-stage → 0.1.0-rc.6

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 (466) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +590 -2
  3. package/dist/access.d.ts +1 -0
  4. package/dist/access.js +2 -0
  5. package/dist/admin/AllocationField.d.ts +2 -0
  6. package/dist/admin/AllocationField.js +80 -0
  7. package/dist/admin/FlagPointSelect.d.ts +2 -0
  8. package/dist/admin/FlagPointSelect.js +54 -0
  9. package/dist/admin/LayerUsage.d.ts +2 -0
  10. package/dist/admin/LayerUsage.js +24 -0
  11. package/dist/admin/LockedFieldNotice.d.ts +2 -0
  12. package/dist/admin/LockedFieldNotice.js +26 -0
  13. package/dist/admin/MetricSourceSelect.d.ts +2 -0
  14. package/dist/admin/MetricSourceSelect.js +32 -0
  15. package/dist/admin/PhaseHistory.d.ts +2 -0
  16. package/dist/admin/PhaseHistory.js +76 -0
  17. package/dist/admin/PreviewLinks.d.ts +11 -0
  18. package/dist/admin/PreviewLinks.js +69 -0
  19. package/dist/admin/StatusCell.d.ts +3 -0
  20. package/dist/admin/StatusCell.js +19 -0
  21. package/dist/admin/SummaryCell.d.ts +2 -0
  22. package/dist/admin/SummaryCell.js +32 -0
  23. package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
  24. package/dist/admin/actions/AddMetricDialog.js +138 -0
  25. package/dist/admin/actions/AllocateDialog.d.ts +6 -0
  26. package/dist/admin/actions/AllocateDialog.js +81 -0
  27. package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
  28. package/dist/admin/actions/ConfirmDialog.js +192 -0
  29. package/dist/admin/actions/DecideDialog.d.ts +7 -0
  30. package/dist/admin/actions/DecideDialog.js +173 -0
  31. package/dist/admin/actions/ExperimentActions.d.ts +3 -0
  32. package/dist/admin/actions/ExperimentActions.js +110 -0
  33. package/dist/admin/actions/MakeContentResult.d.ts +6 -0
  34. package/dist/admin/actions/MakeContentResult.js +50 -0
  35. package/dist/admin/actions/actions.model.d.ts +37 -0
  36. package/dist/admin/actions/actions.model.js +71 -0
  37. package/dist/admin/actions/decide.model.d.ts +21 -0
  38. package/dist/admin/actions/decide.model.js +38 -0
  39. package/dist/admin/allocation.model.d.ts +20 -0
  40. package/dist/admin/allocation.model.js +38 -0
  41. package/dist/admin/api.d.ts +32 -0
  42. package/dist/admin/api.js +95 -0
  43. package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
  44. package/dist/admin/armSelect/ArmSelectField.js +185 -0
  45. package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
  46. package/dist/admin/armSelect/armSelect.model.js +20 -0
  47. package/dist/admin/arms/ArmsField.d.ts +2 -0
  48. package/dist/admin/arms/ArmsField.js +310 -0
  49. package/dist/admin/arms/ParamsEditor.d.ts +16 -0
  50. package/dist/admin/arms/ParamsEditor.js +79 -0
  51. package/dist/admin/arms/arms.model.d.ts +21 -0
  52. package/dist/admin/arms/arms.model.js +153 -0
  53. package/dist/admin/arms/pageTarget.model.d.ts +4 -0
  54. package/dist/admin/arms/pageTarget.model.js +19 -0
  55. package/dist/admin/arms/params.model.d.ts +7 -0
  56. package/dist/admin/arms/params.model.js +35 -0
  57. package/dist/admin/copy/actions.d.ts +38 -0
  58. package/dist/admin/copy/actions.js +38 -0
  59. package/dist/admin/copy/armSelect.d.ts +11 -0
  60. package/dist/admin/copy/armSelect.js +11 -0
  61. package/dist/admin/copy/arms.d.ts +37 -0
  62. package/dist/admin/copy/arms.js +47 -0
  63. package/dist/admin/copy/common.d.ts +20 -0
  64. package/dist/admin/copy/common.js +27 -0
  65. package/dist/admin/copy/history.d.ts +9 -0
  66. package/dist/admin/copy/history.js +9 -0
  67. package/dist/admin/copy/metrics.d.ts +9 -0
  68. package/dist/admin/copy/metrics.js +9 -0
  69. package/dist/admin/copy/results.d.ts +78 -0
  70. package/dist/admin/copy/results.js +81 -0
  71. package/dist/admin/copy/review.d.ts +33 -0
  72. package/dist/admin/copy/review.js +127 -0
  73. package/dist/admin/copy/status.d.ts +8 -0
  74. package/dist/admin/copy/status.js +8 -0
  75. package/dist/admin/copy/tabs.d.ts +7 -0
  76. package/dist/admin/copy/tabs.js +7 -0
  77. package/dist/admin/copy/who.d.ts +36 -0
  78. package/dist/admin/copy/who.js +36 -0
  79. package/dist/admin/labels.d.ts +45 -0
  80. package/dist/admin/labels.js +53 -0
  81. package/dist/admin/layerUsage.model.d.ts +15 -0
  82. package/dist/admin/layerUsage.model.js +18 -0
  83. package/dist/admin/lockedNotice.model.d.ts +3 -0
  84. package/dist/admin/lockedNotice.model.js +18 -0
  85. package/dist/admin/metricSource.model.d.ts +7 -0
  86. package/dist/admin/metricSource.model.js +11 -0
  87. package/dist/admin/paths.d.ts +16 -0
  88. package/dist/admin/paths.js +16 -0
  89. package/dist/admin/permissions.model.d.ts +10 -0
  90. package/dist/admin/permissions.model.js +4 -0
  91. package/dist/admin/phaseHistory.model.d.ts +19 -0
  92. package/dist/admin/phaseHistory.model.js +19 -0
  93. package/dist/admin/reload.d.ts +1 -0
  94. package/dist/admin/reload.js +1 -0
  95. package/dist/admin/results/ArmTable.d.ts +8 -0
  96. package/dist/admin/results/ArmTable.js +112 -0
  97. package/dist/admin/results/CappedCell.d.ts +4 -0
  98. package/dist/admin/results/CappedCell.js +24 -0
  99. package/dist/admin/results/CiChart.d.ts +10 -0
  100. package/dist/admin/results/CiChart.js +178 -0
  101. package/dist/admin/results/DataQualityPanel.d.ts +4 -0
  102. package/dist/admin/results/DataQualityPanel.js +40 -0
  103. package/dist/admin/results/ResultsView.d.ts +3 -0
  104. package/dist/admin/results/ResultsView.js +312 -0
  105. package/dist/admin/results/VerdictCard.d.ts +9 -0
  106. package/dist/admin/results/VerdictCard.js +49 -0
  107. package/dist/admin/results/chart.model.d.ts +32 -0
  108. package/dist/admin/results/chart.model.js +52 -0
  109. package/dist/admin/results/format.d.ts +10 -0
  110. package/dist/admin/results/format.js +55 -0
  111. package/dist/admin/results/results.model.d.ts +105 -0
  112. package/dist/admin/results/results.model.js +175 -0
  113. package/dist/admin/review/ReviewPanel.d.ts +2 -0
  114. package/dist/admin/review/ReviewPanel.js +268 -0
  115. package/dist/admin/review/review.model.d.ts +31 -0
  116. package/dist/admin/review/review.model.js +40 -0
  117. package/dist/admin/status.model.d.ts +16 -0
  118. package/dist/admin/status.model.js +17 -0
  119. package/dist/admin/styles.css +10 -0
  120. package/dist/admin/styles.d.ts +1 -0
  121. package/dist/admin/styles.js +2 -0
  122. package/dist/admin/summaryBatcher.d.ts +8 -0
  123. package/dist/admin/summaryBatcher.js +33 -0
  124. package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
  125. package/dist/admin/targeting/TargetingAttributeField.js +31 -0
  126. package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
  127. package/dist/admin/targeting/TargetingOpField.js +45 -0
  128. package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
  129. package/dist/admin/targeting/TargetingValueField.js +142 -0
  130. package/dist/admin/targeting/targeting.model.d.ts +18 -0
  131. package/dist/admin/targeting/targeting.model.js +48 -0
  132. package/dist/admin/useApi.d.ts +20 -0
  133. package/dist/admin/useApi.js +10 -0
  134. package/dist/admin/useLayerUsage.d.ts +15 -0
  135. package/dist/admin/useLayerUsage.js +92 -0
  136. package/dist/client/ExperimentPageview.d.ts +1 -0
  137. package/dist/client/ExperimentPageview.js +27 -0
  138. package/dist/client/ExperimentsProvider.d.ts +6 -0
  139. package/dist/client/ExperimentsProvider.js +22 -0
  140. package/dist/client/ViewportExposure.d.ts +9 -0
  141. package/dist/client/ViewportExposure.js +69 -0
  142. package/dist/client/beacon.d.ts +17 -0
  143. package/dist/client/beacon.js +71 -0
  144. package/dist/client/context.d.ts +2 -0
  145. package/dist/client/context.js +2 -0
  146. package/dist/client/idempotency.d.ts +2 -0
  147. package/dist/client/idempotency.js +13 -0
  148. package/dist/client/trackConversionBeacon.d.ts +3 -0
  149. package/dist/client/trackConversionBeacon.js +16 -0
  150. package/dist/client/useExperiment.d.ts +5 -0
  151. package/dist/client/useExperiment.js +27 -0
  152. package/dist/client/useFlagPoint.d.ts +9 -0
  153. package/dist/client/useFlagPoint.js +32 -0
  154. package/dist/collections/access.d.ts +21 -0
  155. package/dist/collections/access.js +25 -0
  156. package/dist/collections/events.d.ts +3 -0
  157. package/dist/collections/events.js +117 -0
  158. package/dist/collections/experiments/armInvariants.d.ts +12 -0
  159. package/dist/collections/experiments/armInvariants.js +89 -0
  160. package/dist/collections/experiments/config.d.ts +3 -0
  161. package/dist/collections/experiments/config.js +574 -0
  162. package/dist/collections/experiments/createDefaults.d.ts +2 -0
  163. package/dist/collections/experiments/createDefaults.js +7 -0
  164. package/dist/collections/experiments/deleteGuard.d.ts +3 -0
  165. package/dist/collections/experiments/deleteGuard.js +8 -0
  166. package/dist/collections/experiments/editability.d.ts +4 -0
  167. package/dist/collections/experiments/editability.js +35 -0
  168. package/dist/collections/experiments/flagControlParams.d.ts +2 -0
  169. package/dist/collections/experiments/flagControlParams.js +32 -0
  170. package/dist/collections/experiments/lockHook.d.ts +2 -0
  171. package/dist/collections/experiments/lockHook.js +134 -0
  172. package/dist/collections/identities.d.ts +3 -0
  173. package/dist/collections/identities.js +30 -0
  174. package/dist/collections/index.d.ts +6 -0
  175. package/dist/collections/index.js +24 -0
  176. package/dist/collections/layers.d.ts +3 -0
  177. package/dist/collections/layers.js +77 -0
  178. package/dist/collections/metrics.d.ts +3 -0
  179. package/dist/collections/metrics.js +115 -0
  180. package/dist/collections/phases.d.ts +3 -0
  181. package/dist/collections/phases.js +59 -0
  182. package/dist/collections/serialize.d.ts +5 -0
  183. package/dist/collections/serialize.js +40 -0
  184. package/dist/collections/snapshots.d.ts +3 -0
  185. package/dist/collections/snapshots.js +47 -0
  186. package/dist/constants.d.ts +7 -0
  187. package/dist/constants.js +7 -0
  188. package/dist/db/adapter.d.ts +3 -0
  189. package/dist/db/adapter.js +9 -0
  190. package/dist/db/appends.d.ts +58 -0
  191. package/dist/db/appends.js +116 -0
  192. package/dist/db/delete.d.ts +17 -0
  193. package/dist/db/delete.js +35 -0
  194. package/dist/db/erase.d.ts +12 -0
  195. package/dist/db/erase.js +55 -0
  196. package/dist/db/events.d.ts +21 -0
  197. package/dist/db/events.js +100 -0
  198. package/dist/db/executor.d.ts +10 -0
  199. package/dist/db/executor.js +37 -0
  200. package/dist/db/identities.d.ts +11 -0
  201. package/dist/db/identities.js +29 -0
  202. package/dist/db/locks.d.ts +15 -0
  203. package/dist/db/locks.js +23 -0
  204. package/dist/db/manifest.d.ts +8 -0
  205. package/dist/db/manifest.js +8 -0
  206. package/dist/db/names.d.ts +271 -0
  207. package/dist/db/names.js +270 -0
  208. package/dist/db/pgExecutor.d.ts +41 -0
  209. package/dist/db/pgExecutor.js +250 -0
  210. package/dist/db/results.d.ts +53 -0
  211. package/dist/db/results.js +103 -0
  212. package/dist/db/slices.d.ts +18 -0
  213. package/dist/db/slices.js +51 -0
  214. package/dist/db/snapshots.d.ts +47 -0
  215. package/dist/db/snapshots.js +89 -0
  216. package/dist/db/sql.d.ts +5 -0
  217. package/dist/db/sql.js +5 -0
  218. package/dist/db/state.d.ts +12 -0
  219. package/dist/db/state.js +27 -0
  220. package/dist/db/sweep.d.ts +13 -0
  221. package/dist/db/sweep.js +90 -0
  222. package/dist/db/txid.d.ts +7 -0
  223. package/dist/db/txid.js +12 -0
  224. package/dist/define.d.ts +3 -0
  225. package/dist/define.js +6 -0
  226. package/dist/endpoints/events.d.ts +7 -0
  227. package/dist/endpoints/events.js +98 -0
  228. package/dist/endpoints/index.d.ts +18 -0
  229. package/dist/endpoints/index.js +160 -0
  230. package/dist/endpoints/manifest.d.ts +7 -0
  231. package/dist/endpoints/manifest.js +30 -0
  232. package/dist/endpoints/preflight.d.ts +8 -0
  233. package/dist/endpoints/preflight.js +25 -0
  234. package/dist/endpoints/previewToken.d.ts +7 -0
  235. package/dist/endpoints/previewToken.js +54 -0
  236. package/dist/endpoints/recompute.d.ts +7 -0
  237. package/dist/endpoints/recompute.js +41 -0
  238. package/dist/endpoints/results.d.ts +24 -0
  239. package/dist/endpoints/results.js +136 -0
  240. package/dist/endpoints/summary.d.ts +7 -0
  241. package/dist/endpoints/summary.js +40 -0
  242. package/dist/endpoints/transitions.d.ts +42 -0
  243. package/dist/endpoints/transitions.js +154 -0
  244. package/dist/errors.d.ts +27 -0
  245. package/dist/errors.js +33 -0
  246. package/dist/events/body.d.ts +7 -0
  247. package/dist/events/body.js +43 -0
  248. package/dist/events/dimensions.d.ts +4 -0
  249. package/dist/events/dimensions.js +51 -0
  250. package/dist/events/preview.d.ts +2 -0
  251. package/dist/events/preview.js +9 -0
  252. package/dist/events/request.d.ts +3 -0
  253. package/dist/events/request.js +14 -0
  254. package/dist/events/rows.d.ts +7 -0
  255. package/dist/events/rows.js +65 -0
  256. package/dist/exports/adminClient.d.ts +16 -0
  257. package/dist/exports/adminClient.js +17 -0
  258. package/dist/exports/client.d.ts +7 -0
  259. package/dist/exports/client.js +7 -0
  260. package/dist/exports/index.d.ts +4 -0
  261. package/dist/exports/index.js +3 -0
  262. package/dist/exports/proxy.d.ts +4 -0
  263. package/dist/exports/proxy.js +3 -0
  264. package/dist/exports/puck.d.ts +4 -0
  265. package/dist/exports/puck.js +3 -0
  266. package/dist/exports/puckEditor.d.ts +1 -0
  267. package/dist/exports/puckEditor.js +2 -0
  268. package/dist/exports/rsc.d.ts +13 -0
  269. package/dist/exports/rsc.js +13 -0
  270. package/dist/exports/runtimeProxy.d.ts +4 -0
  271. package/dist/exports/runtimeProxy.js +2 -0
  272. package/dist/exports/runtimeServer.d.ts +2 -0
  273. package/dist/exports/runtimeServer.js +1 -0
  274. package/dist/fields/armSelect.d.ts +7 -0
  275. package/dist/fields/armSelect.js +33 -0
  276. package/dist/globals/state.d.ts +3 -0
  277. package/dist/globals/state.js +31 -0
  278. package/dist/hooks/contentChanges.d.ts +3 -0
  279. package/dist/hooks/contentChanges.js +10 -0
  280. package/dist/hooks/hostHook.d.ts +5 -0
  281. package/dist/hooks/hostHook.js +20 -0
  282. package/dist/index.d.ts +1 -0
  283. package/dist/index.js +4 -0
  284. package/dist/manifest/cache.d.ts +10 -0
  285. package/dist/manifest/cache.js +51 -0
  286. package/dist/manifest/compile.d.ts +63 -0
  287. package/dist/manifest/compile.js +237 -0
  288. package/dist/onInit.d.ts +5 -0
  289. package/dist/onInit.js +13 -0
  290. package/dist/options/fromReq.d.ts +3 -0
  291. package/dist/options/fromReq.js +5 -0
  292. package/dist/options.d.ts +9 -0
  293. package/dist/options.js +107 -0
  294. package/dist/plugin.d.ts +7 -0
  295. package/dist/plugin.js +62 -0
  296. package/dist/proxy/apply.d.ts +3 -0
  297. package/dist/proxy/apply.js +5 -0
  298. package/dist/proxy/consentFallback.d.ts +5 -0
  299. package/dist/proxy/consentFallback.js +27 -0
  300. package/dist/proxy/control.d.ts +6 -0
  301. package/dist/proxy/control.js +36 -0
  302. package/dist/proxy/createProxy.d.ts +3 -0
  303. package/dist/proxy/createProxy.js +15 -0
  304. package/dist/proxy/pageRewrite.d.ts +4 -0
  305. package/dist/proxy/pageRewrite.js +41 -0
  306. package/dist/proxy/resolve.d.ts +13 -0
  307. package/dist/proxy/resolve.js +163 -0
  308. package/dist/proxy/skip.d.ts +1 -0
  309. package/dist/proxy/skip.js +9 -0
  310. package/dist/puck/ExperimentPicker.d.ts +5 -0
  311. package/dist/puck/ExperimentPicker.js +88 -0
  312. package/dist/puck/ExperimentSlot.d.ts +3 -0
  313. package/dist/puck/ExperimentSlot.js +16 -0
  314. package/dist/puck/copy.d.ts +14 -0
  315. package/dist/puck/copy.js +14 -0
  316. package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
  317. package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
  318. package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
  319. package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
  320. package/dist/puck/editor/cloneSlot.d.ts +3 -0
  321. package/dist/puck/editor/cloneSlot.js +41 -0
  322. package/dist/puck/editor/copy.d.ts +25 -0
  323. package/dist/puck/editor/copy.js +25 -0
  324. package/dist/puck/editor/editor.model.d.ts +16 -0
  325. package/dist/puck/editor/editor.model.js +33 -0
  326. package/dist/puck/experimentSlotField.d.ts +5 -0
  327. package/dist/puck/experimentSlotField.js +13 -0
  328. package/dist/puck/slot.model.d.ts +2 -0
  329. package/dist/puck/slot.model.js +11 -0
  330. package/dist/puck/slotConfig.d.ts +5 -0
  331. package/dist/puck/slotConfig.js +25 -0
  332. package/dist/results/aggregate.d.ts +34 -0
  333. package/dist/results/aggregate.js +66 -0
  334. package/dist/results/analyse.d.ts +79 -0
  335. package/dist/results/analyse.js +378 -0
  336. package/dist/results/cuped.d.ts +16 -0
  337. package/dist/results/cuped.js +31 -0
  338. package/dist/results/decideGuard.d.ts +3 -0
  339. package/dist/results/decideGuard.js +8 -0
  340. package/dist/results/guardrails.d.ts +17 -0
  341. package/dist/results/guardrails.js +10 -0
  342. package/dist/results/history.d.ts +13 -0
  343. package/dist/results/history.js +39 -0
  344. package/dist/results/identityCredit.d.ts +16 -0
  345. package/dist/results/identityCredit.js +46 -0
  346. package/dist/results/power.d.ts +15 -0
  347. package/dist/results/power.js +60 -0
  348. package/dist/results/rolloutUnits.d.ts +17 -0
  349. package/dist/results/rolloutUnits.js +26 -0
  350. package/dist/results/snapshot.d.ts +9 -0
  351. package/dist/results/snapshot.js +154 -0
  352. package/dist/results/srm.d.ts +9 -0
  353. package/dist/results/srm.js +22 -0
  354. package/dist/results/verdict.d.ts +82 -0
  355. package/dist/results/verdict.js +85 -0
  356. package/dist/runtime/assign.d.ts +23 -0
  357. package/dist/runtime/assign.js +104 -0
  358. package/dist/runtime/bot.d.ts +1 -0
  359. package/dist/runtime/bot.js +7 -0
  360. package/dist/runtime/cookie.d.ts +17 -0
  361. package/dist/runtime/cookie.js +35 -0
  362. package/dist/runtime/eligibility.d.ts +22 -0
  363. package/dist/runtime/eligibility.js +20 -0
  364. package/dist/runtime/header.d.ts +17 -0
  365. package/dist/runtime/header.js +97 -0
  366. package/dist/runtime/loaders.d.ts +17 -0
  367. package/dist/runtime/loaders.js +29 -0
  368. package/dist/runtime/normalisePath.d.ts +7 -0
  369. package/dist/runtime/normalisePath.js +15 -0
  370. package/dist/runtime/previewToken.d.ts +9 -0
  371. package/dist/runtime/previewToken.js +23 -0
  372. package/dist/runtime/proxyRuntime.d.ts +2 -0
  373. package/dist/runtime/proxyRuntime.js +22 -0
  374. package/dist/runtime/seed.d.ts +2 -0
  375. package/dist/runtime/seed.js +4 -0
  376. package/dist/runtime/signing.d.ts +3 -0
  377. package/dist/runtime/signing.js +16 -0
  378. package/dist/runtime/targeting.d.ts +6 -0
  379. package/dist/runtime/targeting.js +9 -0
  380. package/dist/server/context.d.ts +10 -0
  381. package/dist/server/context.js +59 -0
  382. package/dist/server/eraseUser.d.ts +7 -0
  383. package/dist/server/eraseUser.js +4 -0
  384. package/dist/server/getAssignments.d.ts +9 -0
  385. package/dist/server/getAssignments.js +227 -0
  386. package/dist/server/getFlagPoint.d.ts +11 -0
  387. package/dist/server/getFlagPoint.js +36 -0
  388. package/dist/server/headerRevision.d.ts +6 -0
  389. package/dist/server/headerRevision.js +10 -0
  390. package/dist/server/linkIdentity.d.ts +9 -0
  391. package/dist/server/linkIdentity.js +34 -0
  392. package/dist/server/metadata.d.ts +7 -0
  393. package/dist/server/metadata.js +24 -0
  394. package/dist/server/previewDraft.d.ts +7 -0
  395. package/dist/server/previewDraft.js +51 -0
  396. package/dist/server/recordContentChange.d.ts +7 -0
  397. package/dist/server/recordContentChange.js +150 -0
  398. package/dist/server/recordExposures.d.ts +7 -0
  399. package/dist/server/recordExposures.js +56 -0
  400. package/dist/server/request.d.ts +7 -0
  401. package/dist/server/request.js +39 -0
  402. package/dist/server/resolveExperimentData.d.ts +6 -0
  403. package/dist/server/resolveExperimentData.js +64 -0
  404. package/dist/server/runResults.d.ts +22 -0
  405. package/dist/server/runResults.js +307 -0
  406. package/dist/server/serverRuntime.d.ts +7 -0
  407. package/dist/server/serverRuntime.js +20 -0
  408. package/dist/server/sitemap.d.ts +3 -0
  409. package/dist/server/sitemap.js +6 -0
  410. package/dist/server/toClientAssignments.d.ts +7 -0
  411. package/dist/server/toClientAssignments.js +56 -0
  412. package/dist/server/trackConversion.d.ts +12 -0
  413. package/dist/server/trackConversion.js +69 -0
  414. package/dist/targeting/ops.d.ts +8 -0
  415. package/dist/targeting/ops.js +58 -0
  416. package/dist/transitions/addMetric.d.ts +16 -0
  417. package/dist/transitions/addMetric.js +126 -0
  418. package/dist/transitions/allocate.d.ts +9 -0
  419. package/dist/transitions/allocate.js +84 -0
  420. package/dist/transitions/archive.d.ts +9 -0
  421. package/dist/transitions/archive.js +46 -0
  422. package/dist/transitions/checkStart.d.ts +61 -0
  423. package/dist/transitions/checkStart.js +104 -0
  424. package/dist/transitions/confirmPhrases.d.ts +5 -0
  425. package/dist/transitions/confirmPhrases.js +18 -0
  426. package/dist/transitions/decide.d.ts +9 -0
  427. package/dist/transitions/decide.js +138 -0
  428. package/dist/transitions/deleteExperiment.d.ts +5 -0
  429. package/dist/transitions/deleteExperiment.js +66 -0
  430. package/dist/transitions/kernel.d.ts +31 -0
  431. package/dist/transitions/kernel.js +69 -0
  432. package/dist/transitions/makeContent.d.ts +20 -0
  433. package/dist/transitions/makeContent.js +411 -0
  434. package/dist/transitions/phaseRecord.d.ts +27 -0
  435. package/dist/transitions/phaseRecord.js +81 -0
  436. package/dist/transitions/placements.d.ts +26 -0
  437. package/dist/transitions/placements.js +89 -0
  438. package/dist/transitions/preflightData.d.ts +21 -0
  439. package/dist/transitions/preflightData.js +317 -0
  440. package/dist/transitions/releaseHoldout.d.ts +8 -0
  441. package/dist/transitions/releaseHoldout.js +30 -0
  442. package/dist/transitions/resolveCaps.d.ts +28 -0
  443. package/dist/transitions/resolveCaps.js +68 -0
  444. package/dist/transitions/start.d.ts +26 -0
  445. package/dist/transitions/start.js +220 -0
  446. package/dist/transitions/stop.d.ts +8 -0
  447. package/dist/transitions/stop.js +25 -0
  448. package/dist/types/admin.d.ts +20 -0
  449. package/dist/types/admin.js +1 -0
  450. package/dist/types/assignments.d.ts +57 -0
  451. package/dist/types/assignments.js +1 -0
  452. package/dist/types/contracts.d.ts +141 -0
  453. package/dist/types/contracts.js +1 -0
  454. package/dist/types/endpoints.d.ts +135 -0
  455. package/dist/types/endpoints.js +1 -0
  456. package/dist/types/index.d.ts +8 -0
  457. package/dist/types/index.js +8 -0
  458. package/dist/types/manifest.d.ts +42 -0
  459. package/dist/types/manifest.js +1 -0
  460. package/dist/types/puck.d.ts +23 -0
  461. package/dist/types/puck.js +1 -0
  462. package/dist/types/runtime.d.ts +59 -0
  463. package/dist/types/runtime.js +1 -0
  464. package/dist/types/snapshot.d.ts +117 -0
  465. package/dist/types/snapshot.js +1 -0
  466. package/package.json +138 -3
@@ -0,0 +1,310 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import { Button, TextInput, useField, useForm, useFormFields } from '@payloadcms/ui';
5
+ import { EDITABLE_BY_STATUS, isEditable } from '../../collections/experiments/editability.js';
6
+ import { lockedReason } from '../lockedNotice.model.js';
7
+ import { ARMS_COPY, GROUP_LABELS, ISSUE_COPY, issueMessage } from '../copy/arms.js';
8
+ import { addHoldoutArm, addVariant, ARM_FIELDS, defaultArms, issuesByRow, readArms, removeArm, setWeight, splitEvenly, totalWeight, withSlotsFor } from './arms.model.js';
9
+ import { pageTargetIssue, setPageTarget, showsPageTarget } from './pageTarget.model.js';
10
+ import { paramsForArm, paramsReadOnly } from './params.model.js';
11
+ import { numberInputStyle, ParamsEditor } from './ParamsEditor.js';
12
+ function newRowId() {
13
+ if (globalThis.crypto?.getRandomValues) {
14
+ return Array.from(globalThis.crypto.getRandomValues(new Uint8Array(12)), (byte)=>byte.toString(16).padStart(2, '0')).join('');
15
+ }
16
+ return (Date.now().toString(16).padStart(12, '0') + Math.floor(Math.random() * 2 ** 48).toString(16).padStart(12, '0')).slice(-24);
17
+ }
18
+ export function ArmsField({ field, path = 'arms', readOnly }) {
19
+ const form = useForm();
20
+ const { showError, errorMessage } = useField({
21
+ path,
22
+ hasRows: true
23
+ });
24
+ const kind = useFormFields(([fields])=>fields.kind?.value);
25
+ const status = useFormFields(([fields])=>fields.status?.value ?? 'draft');
26
+ const flagPoint = useFormFields(([fields])=>fields.flagPoint?.value);
27
+ const rowMetadata = useFormFields(([fields])=>fields[path]?.rows);
28
+ // Payload compares selector results by identity. Retain the arm slice when
29
+ // another field changes, including while a cleared weight contains NaN.
30
+ const selectRows = useMemo(()=>{
31
+ let previous = {
32
+ arms: [],
33
+ rowIds: []
34
+ };
35
+ return ([fields])=>{
36
+ const arms = readArms(fields, path);
37
+ const rowIds = arms.map((_, index)=>fields[`${path}.${index}.id`]?.value);
38
+ if (arms.length !== previous.arms.length || arms.some((arm, index)=>rowIds[index] !== previous.rowIds[index] || ARM_FIELDS.some((key)=>!Object.is(arm[key], previous.arms[index]?.[key])))) previous = {
39
+ arms,
40
+ rowIds
41
+ };
42
+ return previous;
43
+ };
44
+ }, [
45
+ path
46
+ ]);
47
+ const { arms, rowIds } = useFormFields(selectRows);
48
+ const seeded = useRef(false);
49
+ const editable = isEditable(status, 'arms') && !readOnly && !form.disabled;
50
+ const reason = lockedReason(status, 'arms', EDITABLE_BY_STATUS);
51
+ const points = field.admin?.custom?.flagPoints ?? [];
52
+ const point = points.find((candidate)=>candidate.id === flagPoint);
53
+ const issues = issuesByRow(arms, kind);
54
+ const total = totalWeight(arms);
55
+ const hasControl = arms.some((arm)=>arm.kind === 'control');
56
+ const serverCode = [
57
+ ...issues.form,
58
+ ...issues.rows.flat()
59
+ ].find((issue)=>issue.message === errorMessage)?.code ?? (typeof errorMessage === 'string' ? Object.keys(ISSUE_COPY).find((code)=>errorMessage.split(/[^a-z-]+/).includes(code)) : undefined);
60
+ function apply(next) {
61
+ if (!editable) return;
62
+ // null (from Postgres) and undefined both mean "unset"; only real differences are written.
63
+ const same = (a, b)=>Object.is(a, b) || a == null && b == null;
64
+ let changed = false;
65
+ next = withSlotsFor(next, kind);
66
+ if (next === arms) return;
67
+ const structureChanged = next.length !== arms.length || next.some((row, index)=>row.key !== arms[index]?.key);
68
+ // Array row IDs identify existing groups in the server's immutable-key check.
69
+ // Move both the hidden ID field and public row metadata with the logical key.
70
+ const idsByKey = new Map(arms.map((row, index)=>[
71
+ row.key,
72
+ rowIds[index]
73
+ ]));
74
+ const nextIds = structureChanged ? next.map((row)=>idsByKey.get(row.key) ?? newRowId()) : rowIds;
75
+ if (structureChanged) changed = true;
76
+ for(let rowIndex = arms.length - 1; rowIndex >= next.length; rowIndex--)form.removeFieldRow({
77
+ path,
78
+ rowIndex
79
+ });
80
+ for(let rowIndex = arms.length; rowIndex < next.length; rowIndex++){
81
+ const row = next[rowIndex];
82
+ const subFieldState = {
83
+ ...Object.fromEntries(ARM_FIELDS.filter((key)=>key !== 'params' || row.kind !== 'holdout').map((key)=>[
84
+ key,
85
+ {
86
+ value: row[key],
87
+ initialValue: undefined,
88
+ valid: true
89
+ }
90
+ ])),
91
+ id: {
92
+ value: nextIds[rowIndex],
93
+ initialValue: undefined,
94
+ valid: true
95
+ }
96
+ };
97
+ form.addFieldRow({
98
+ path,
99
+ rowIndex,
100
+ schemaPath: 'arms',
101
+ subFieldState
102
+ });
103
+ }
104
+ next.forEach((row, index)=>{
105
+ if (structureChanged && rowIds[index] !== nextIds[index]) form.dispatchFields({
106
+ type: 'UPDATE',
107
+ path: `${path}.${index}.id`,
108
+ value: nextIds[index]
109
+ });
110
+ for (const key of ARM_FIELDS){
111
+ if (key === 'params' && row.kind === 'holdout') {
112
+ // Reusing a physical row for the holdout must drop the former
113
+ // variant's params without assigning params to the holdout.
114
+ if (arms[index]?.params != null) {
115
+ changed = true;
116
+ form.dispatchFields({
117
+ type: 'UPDATE',
118
+ path: `${path}.${index}.params`,
119
+ value: undefined
120
+ });
121
+ }
122
+ continue;
123
+ }
124
+ if (!same(arms[index]?.[key], row[key])) {
125
+ changed = true;
126
+ form.dispatchFields({
127
+ type: 'UPDATE',
128
+ path: `${path}.${index}.${key}`,
129
+ value: row[key]
130
+ });
131
+ }
132
+ }
133
+ });
134
+ if (structureChanged) form.dispatchFields({
135
+ type: 'UPDATE',
136
+ path,
137
+ rows: nextIds.map((id)=>rowMetadata?.find((row)=>row.id === id) ?? {
138
+ id: id,
139
+ isLoading: true
140
+ })
141
+ });
142
+ if (changed) form.setModified(true);
143
+ }
144
+ useEffect(()=>{
145
+ if (!editable) return;
146
+ if (!arms.length && !seeded.current) {
147
+ seeded.current = true;
148
+ apply(defaultArms(kind));
149
+ } else if (arms.length) apply(arms);
150
+ });
151
+ function update(index, patch) {
152
+ apply(arms.map((arm, i)=>i === index ? {
153
+ ...arm,
154
+ ...patch
155
+ } : arm));
156
+ }
157
+ return /*#__PURE__*/ _jsxs("div", {
158
+ className: "xp-groups",
159
+ children: [
160
+ reason ? /*#__PURE__*/ _jsx("p", {
161
+ className: "xp-locked-notice",
162
+ children: reason
163
+ }) : null,
164
+ arms.length ? issues.form.map((issue, i)=>/*#__PURE__*/ _jsx("p", {
165
+ role: "alert",
166
+ children: issueMessage(issue, total)
167
+ }, i)) : null,
168
+ showError && errorMessage ? /*#__PURE__*/ _jsx("p", {
169
+ role: "alert",
170
+ children: issueMessage({
171
+ code: serverCode ?? '',
172
+ message: errorMessage
173
+ }, total)
174
+ }) : null,
175
+ /*#__PURE__*/ _jsxs("fieldset", {
176
+ disabled: !editable,
177
+ "aria-label": ARMS_COPY.groups,
178
+ style: {
179
+ border: 0,
180
+ padding: 0,
181
+ margin: 0
182
+ },
183
+ children: [
184
+ arms.map((arm, index)=>{
185
+ const rowPath = `${path}.${index}`;
186
+ const pageIssue = kind && showsPageTarget(kind, arm.kind) ? pageTargetIssue(arm.pageTarget, arm.kind) : null;
187
+ return /*#__PURE__*/ _jsxs("section", {
188
+ className: "xp-group",
189
+ style: {
190
+ marginBottom: '1.5rem'
191
+ },
192
+ children: [
193
+ /*#__PURE__*/ _jsx("h3", {
194
+ children: GROUP_LABELS[arm.key] ?? arm.label
195
+ }),
196
+ /*#__PURE__*/ _jsx(TextInput, {
197
+ hasMany: false,
198
+ path: `${rowPath}.label`,
199
+ label: ARMS_COPY.label,
200
+ value: arm.label ?? '',
201
+ readOnly: !editable,
202
+ onChange: (event)=>update(index, {
203
+ label: event.target.value
204
+ })
205
+ }),
206
+ /*#__PURE__*/ _jsxs("label", {
207
+ htmlFor: `${rowPath}.weight`,
208
+ children: [
209
+ ARMS_COPY.weight,
210
+ /*#__PURE__*/ _jsx("input", {
211
+ id: `${rowPath}.weight`,
212
+ name: `${rowPath}.weight`,
213
+ type: "number",
214
+ min: 0,
215
+ max: 100,
216
+ step: 1,
217
+ style: numberInputStyle,
218
+ disabled: !editable,
219
+ value: Number.isFinite(arm.weight) ? arm.weight : '',
220
+ onChange: (event)=>apply(setWeight(arms, arm.key, event.target.value === '' ? NaN : Number(event.target.value)))
221
+ })
222
+ ]
223
+ }),
224
+ kind === 'block' && arm.kind !== 'holdout' ? /*#__PURE__*/ _jsxs("p", {
225
+ children: [
226
+ ARMS_COPY.slot,
227
+ ": ",
228
+ GROUP_LABELS[arm.key] ?? arm.label
229
+ ]
230
+ }) : null,
231
+ kind === 'page' && arm.kind === 'holdout' ? /*#__PURE__*/ _jsx("p", {
232
+ children: ARMS_COPY.holdoutPage
233
+ }) : null,
234
+ kind && showsPageTarget(kind, arm.kind) ? /*#__PURE__*/ _jsx(TextInput, {
235
+ hasMany: false,
236
+ path: `${rowPath}.pageTarget`,
237
+ label: ARMS_COPY.pagePath,
238
+ value: arm.pageTarget ?? '',
239
+ required: true,
240
+ readOnly: !editable,
241
+ onChange: (event)=>{
242
+ const next = setPageTarget(arm, event.target.value);
243
+ update(index, {
244
+ pageTarget: next.pageTarget
245
+ });
246
+ }
247
+ }) : null,
248
+ pageIssue ? /*#__PURE__*/ _jsx("p", {
249
+ role: "alert",
250
+ children: pageIssue
251
+ }) : null,
252
+ kind === 'flag' && point && arm.kind !== 'holdout' ? /*#__PURE__*/ _jsxs(_Fragment, {
253
+ children: [
254
+ arm.kind === 'control' ? /*#__PURE__*/ _jsx("p", {
255
+ children: ARMS_COPY.controlParams
256
+ }) : null,
257
+ /*#__PURE__*/ _jsx(ParamsEditor, {
258
+ fields: point.params,
259
+ path: `${rowPath}.params`,
260
+ value: paramsForArm(arm, point),
261
+ readOnly: paramsReadOnly(arm, editable),
262
+ onChange: (params)=>update(index, {
263
+ params
264
+ })
265
+ })
266
+ ]
267
+ }) : null,
268
+ issues.rows[index].map((issue, i)=>/*#__PURE__*/ _jsx("p", {
269
+ role: "alert",
270
+ children: issueMessage(issue, total)
271
+ }, i)),
272
+ arm.kind === 'holdout' ? /*#__PURE__*/ _jsxs(_Fragment, {
273
+ children: [
274
+ /*#__PURE__*/ _jsx("p", {
275
+ children: ARMS_COPY.holdout
276
+ }),
277
+ /*#__PURE__*/ _jsx(Button, {
278
+ onClick: ()=>apply(removeArm(arms, arm.key)),
279
+ disabled: !editable,
280
+ children: ARMS_COPY.removeHoldout
281
+ })
282
+ ]
283
+ }) : arm.kind === 'variant' ? /*#__PURE__*/ _jsx(Button, {
284
+ onClick: ()=>apply(removeArm(arms, arm.key)),
285
+ disabled: !editable,
286
+ children: ARMS_COPY.removeVariant
287
+ }) : null
288
+ ]
289
+ }, index);
290
+ }),
291
+ /*#__PURE__*/ _jsx(Button, {
292
+ disabled: !editable || !hasControl || arms.filter((arm)=>arm.kind === 'variant').length >= 3,
293
+ onClick: ()=>apply(addVariant(arms, kind)),
294
+ children: ARMS_COPY.addVariant
295
+ }),
296
+ !arms.some((arm)=>arm.kind === 'holdout') ? /*#__PURE__*/ _jsx(Button, {
297
+ disabled: !editable || !hasControl,
298
+ onClick: ()=>apply(addHoldoutArm(arms)),
299
+ children: ARMS_COPY.addHoldout
300
+ }) : null,
301
+ /*#__PURE__*/ _jsx(Button, {
302
+ disabled: !editable,
303
+ onClick: ()=>apply(splitEvenly(arms)),
304
+ children: ARMS_COPY.splitEvenly
305
+ })
306
+ ]
307
+ })
308
+ ]
309
+ });
310
+ }
@@ -0,0 +1,16 @@
1
+ import type { ParamField } from '../../types/contracts.js';
2
+ export declare const numberInputStyle: {
3
+ readonly background: "var(--theme-input-bg)";
4
+ readonly color: "var(--theme-text)";
5
+ readonly border: "1px solid var(--theme-elevation-150)";
6
+ readonly borderRadius: "var(--style-radius-s)";
7
+ readonly padding: "0.65rem";
8
+ readonly width: "100%";
9
+ };
10
+ export declare function ParamsEditor({ fields, value, path, readOnly, onChange }: {
11
+ fields: Record<string, ParamField>;
12
+ value: Record<string, unknown>;
13
+ path: string;
14
+ readOnly: boolean;
15
+ onChange: (value: Record<string, unknown>) => void;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { CheckboxInput, SelectInput, TextareaInput, TextInput } from '@payloadcms/ui';
4
+ import { paramIssue, setParam } from './params.model.js';
5
+ export const numberInputStyle = {
6
+ background: 'var(--theme-input-bg)',
7
+ color: 'var(--theme-text)',
8
+ border: '1px solid var(--theme-elevation-150)',
9
+ borderRadius: 'var(--style-radius-s)',
10
+ padding: '0.65rem',
11
+ width: '100%'
12
+ };
13
+ export function ParamsEditor({ fields, value, path, readOnly, onChange }) {
14
+ return /*#__PURE__*/ _jsx("div", {
15
+ className: "xp-params-editor",
16
+ children: Object.entries(fields).map(([key, field])=>{
17
+ const fieldPath = `${path}.${key}`;
18
+ const change = (next)=>{
19
+ if (!readOnly) onChange(setParam(value, key, field, next));
20
+ };
21
+ const issue = paramIssue(field, value[key]);
22
+ const shared = {
23
+ path: fieldPath,
24
+ label: field.label,
25
+ required: field.required,
26
+ readOnly
27
+ };
28
+ const text = typeof value[key] === 'string' ? value[key] : '';
29
+ return /*#__PURE__*/ _jsxs("div", {
30
+ children: [
31
+ field.type === 'number' ? /*#__PURE__*/ _jsxs("label", {
32
+ htmlFor: fieldPath,
33
+ children: [
34
+ field.label,
35
+ /*#__PURE__*/ _jsx("input", {
36
+ id: fieldPath,
37
+ name: fieldPath,
38
+ type: "number",
39
+ style: numberInputStyle,
40
+ required: field.required,
41
+ disabled: readOnly,
42
+ min: field.min,
43
+ max: field.max,
44
+ step: "any",
45
+ value: typeof value[key] === 'number' && Number.isFinite(value[key]) ? value[key] : '',
46
+ "aria-invalid": !!issue,
47
+ onChange: (event)=>change(event.target.value)
48
+ })
49
+ ]
50
+ }) : field.type === 'checkbox' ? /*#__PURE__*/ _jsx(CheckboxInput, {
51
+ ...shared,
52
+ name: fieldPath,
53
+ checked: value[key] === true,
54
+ onToggle: (event)=>change(event.target.checked)
55
+ }) : field.type === 'select' ? /*#__PURE__*/ _jsx(SelectInput, {
56
+ ...shared,
57
+ name: fieldPath,
58
+ options: field.options ?? [],
59
+ value: text,
60
+ onChange: (option)=>change(!Array.isArray(option) ? option?.value ?? '' : '')
61
+ }) : field.type === 'textarea' ? /*#__PURE__*/ _jsx(TextareaInput, {
62
+ ...shared,
63
+ value: text,
64
+ onChange: (event)=>change(event.target.value)
65
+ }) : /*#__PURE__*/ _jsx(TextInput, {
66
+ hasMany: false,
67
+ ...shared,
68
+ value: text,
69
+ onChange: (event)=>change(event.target.value)
70
+ }),
71
+ issue ? /*#__PURE__*/ _jsx("p", {
72
+ role: "alert",
73
+ children: issue
74
+ }) : null
75
+ ]
76
+ }, key);
77
+ })
78
+ });
79
+ }
@@ -0,0 +1,21 @@
1
+ import type { ArmIssue } from '../../collections/experiments/armInvariants.js';
2
+ import type { Arm, ArmKey, ExperimentKind } from '../../types/contracts.js';
3
+ export declare function defaultArms(kind?: ExperimentKind): Arm[];
4
+ /** Keep content slots in step with the experiment kind, including an unset kind. */
5
+ export declare function withSlotsFor(arms: Arm[], kind?: ExperimentKind): Arm[];
6
+ export declare function totalWeight(arms: Arm[]): number;
7
+ export declare function addVariant(arms: Arm[], kind?: ExperimentKind): Arm[];
8
+ export declare function removeArm(arms: Arm[], key: ArmKey): Arm[];
9
+ export declare function addHoldoutArm(arms: Arm[]): Arm[];
10
+ export declare function removeHoldout(arms: Arm[]): Arm[];
11
+ export declare function splitEvenly(arms: Arm[]): Arm[];
12
+ export declare function setWeight(arms: Arm[], key: ArmKey, weight: number): Arm[];
13
+ export declare function issuesByRow(arms: Arm[], kind?: ExperimentKind): {
14
+ rows: ArmIssue[][];
15
+ form: ArmIssue[];
16
+ };
17
+ export declare const ARM_FIELDS: readonly ["key", "label", "weight", "kind", "slot", "params", "pageTarget"];
18
+ /** Payload arrays store each row's values at separate field paths. */
19
+ export declare function readArms(fields: Record<string, {
20
+ value?: unknown;
21
+ }>, path?: string): Arm[];
@@ -0,0 +1,153 @@
1
+ import { addHoldout, checkArms } from '../../collections/experiments/armInvariants.js';
2
+ import { GROUP_LABELS } from '../copy/arms.js';
3
+ const VARIANTS = [
4
+ 'variantA',
5
+ 'variantB',
6
+ 'variantC'
7
+ ];
8
+ export function defaultArms(kind) {
9
+ return withSlotsFor([
10
+ {
11
+ key: 'control',
12
+ kind: 'control',
13
+ label: GROUP_LABELS.control,
14
+ weight: 50
15
+ },
16
+ {
17
+ key: 'variantA',
18
+ kind: 'variant',
19
+ label: GROUP_LABELS.variantA,
20
+ weight: 50
21
+ }
22
+ ], kind);
23
+ }
24
+ /** Keep content slots in step with the experiment kind, including an unset kind. */ export function withSlotsFor(arms, kind) {
25
+ const next = arms.map((arm)=>{
26
+ if (kind === 'block' && arm.kind !== 'holdout') return arm.slot === arm.key ? arm : {
27
+ ...arm,
28
+ slot: arm.key
29
+ };
30
+ // Postgres returns null for unset columns; null counts as "no slot" so a loaded doc is not rewritten.
31
+ if (arm.slot == null) return arm;
32
+ const { slot: _slot, ...rest } = arm;
33
+ return rest;
34
+ });
35
+ return next.every((arm, index)=>arm === arms[index]) ? arms : next;
36
+ }
37
+ const finiteWeight = (weight)=>Number.isFinite(weight) ? weight : 0;
38
+ export function totalWeight(arms) {
39
+ return arms.reduce((sum, arm)=>sum + finiteWeight(arm.weight), 0);
40
+ }
41
+ export function addVariant(arms, kind) {
42
+ if (!arms.some((arm)=>arm.kind === 'control')) return arms;
43
+ const key = VARIANTS.find((candidate)=>!arms.some((arm)=>arm.key === candidate));
44
+ if (!key || arms.filter((arm)=>arm.kind === 'variant').length >= 3) return arms;
45
+ const block = kind === 'block' || kind === undefined && arms.some((arm)=>arm.slot !== undefined);
46
+ const variant = {
47
+ key,
48
+ kind: 'variant',
49
+ label: GROUP_LABELS[key],
50
+ weight: 0,
51
+ ...block ? {
52
+ slot: key
53
+ } : {}
54
+ };
55
+ const index = arms.findIndex((arm)=>arm.kind === 'holdout');
56
+ const result = [
57
+ ...arms
58
+ ];
59
+ result.splice(index < 0 ? arms.length : index, 0, variant);
60
+ return result;
61
+ }
62
+ export function removeArm(arms, key) {
63
+ if (arms.some((arm)=>arm.key === key && arm.kind === 'control')) return arms;
64
+ if (key === 'holdout') return removeHoldout(arms);
65
+ return arms.filter((arm)=>arm.key !== key);
66
+ }
67
+ export function addHoldoutArm(arms) {
68
+ if (!arms.some((arm)=>arm.kind === 'control') || arms.some((arm)=>arm.kind === 'holdout' || arm.key === 'holdout')) return arms;
69
+ return addHoldout(arms.map((arm)=>Number.isFinite(arm.weight) ? arm : {
70
+ ...arm,
71
+ weight: 0
72
+ }));
73
+ }
74
+ // Largest remainder, with equal fractional remainders awarded to Original first.
75
+ function distribute(arms, total, proportional) {
76
+ if (!arms.length) return arms;
77
+ const weightTotal = totalWeight(arms);
78
+ const useWeights = proportional && weightTotal > 0;
79
+ const denominator = useWeights ? weightTotal : arms.length;
80
+ const numerators = arms.map((arm)=>(useWeights ? finiteWeight(arm.weight) : 1) * total);
81
+ const weights = numerators.map((numerator)=>Math.floor(numerator / denominator));
82
+ const order = arms.map((_, i)=>i).sort((a, b)=>numerators[b] % denominator - numerators[a] % denominator || Number(arms[b].kind === 'control') - Number(arms[a].kind === 'control') || a - b);
83
+ const remainder = total - weights.reduce((sum, weight)=>sum + weight, 0);
84
+ for(let i = 0; i < remainder; i++)weights[order[i % order.length]]++;
85
+ return arms.map((arm, i)=>({
86
+ ...arm,
87
+ weight: weights[i]
88
+ }));
89
+ }
90
+ export function removeHoldout(arms) {
91
+ if (!arms.some((arm)=>arm.kind === 'holdout')) return arms;
92
+ return distribute(arms.filter((arm)=>arm.kind !== 'holdout'), totalWeight(arms), true);
93
+ }
94
+ export function splitEvenly(arms) {
95
+ const holdout = arms.find((arm)=>arm.kind === 'holdout');
96
+ const weight = finiteWeight(holdout?.weight ?? 0);
97
+ return [
98
+ ...distribute(arms.filter((arm)=>arm.kind !== 'holdout'), 100 - weight, false),
99
+ ...holdout ? [
100
+ {
101
+ ...holdout,
102
+ weight
103
+ }
104
+ ] : []
105
+ ];
106
+ }
107
+ export function setWeight(arms, key, weight) {
108
+ return arms.map((arm)=>arm.key === key ? {
109
+ ...arm,
110
+ weight
111
+ } : arm);
112
+ }
113
+ export function issuesByRow(arms, kind) {
114
+ const rows = arms.map(()=>[]);
115
+ const form = [];
116
+ for (const issue of checkArms(arms, kind)){
117
+ const index = issue.armIndex ?? arms.findIndex((arm)=>arm.key === issue.armKey);
118
+ if (index >= 0 && index < arms.length) rows[index].push(issue);
119
+ else form.push(issue);
120
+ }
121
+ return {
122
+ rows,
123
+ form
124
+ };
125
+ }
126
+ export const ARM_FIELDS = [
127
+ 'key',
128
+ 'label',
129
+ 'weight',
130
+ 'kind',
131
+ 'slot',
132
+ 'params',
133
+ 'pageTarget'
134
+ ];
135
+ /** Payload arrays store each row's values at separate field paths. */ export function readArms(fields, path = 'arms') {
136
+ const indices = new Set();
137
+ for (const name of Object.keys(fields)){
138
+ if (!name.startsWith(`${path}.`)) continue;
139
+ const [index, field] = name.slice(path.length + 1).split('.');
140
+ if (/^\d+$/.test(index) && field === 'key') indices.add(Number(index));
141
+ }
142
+ return [
143
+ ...indices
144
+ ].sort((a, b)=>a - b).map((index)=>Object.fromEntries(ARM_FIELDS.flatMap((field)=>{
145
+ const value = fields[`${path}.${index}.${field}`]?.value;
146
+ return value === undefined ? [] : [
147
+ [
148
+ field,
149
+ value
150
+ ]
151
+ ];
152
+ })));
153
+ }
@@ -0,0 +1,4 @@
1
+ import type { Arm, ArmKind, ExperimentKind } from '../../types/contracts.js';
2
+ export declare function showsPageTarget(kind: ExperimentKind, group: ArmKind): boolean;
3
+ export declare function pageTargetIssue(value: unknown, group: ArmKind): string | null;
4
+ export declare function setPageTarget(arm: Arm, value: string): Arm;
@@ -0,0 +1,19 @@
1
+ import { ARMS_COPY } from '../copy/arms.js';
2
+ export function showsPageTarget(kind, group) {
3
+ return kind === 'page' && group !== 'holdout';
4
+ }
5
+ export function pageTargetIssue(value, group) {
6
+ if (group === 'holdout') return null;
7
+ if (typeof value !== 'string' || !value.trim()) return ARMS_COPY.pageRequired;
8
+ // Backslashes and whitespace can be normalised into a different URL by the browser.
9
+ if (!value.startsWith('/') || value.startsWith('//') || /[?#\\\s]/.test(value)) return ARMS_COPY.pageInvalid;
10
+ return null;
11
+ }
12
+ export function setPageTarget(arm, value) {
13
+ if (arm.kind !== 'holdout') return {
14
+ ...arm,
15
+ pageTarget: value
16
+ };
17
+ const { pageTarget: _ignored, ...rest } = arm;
18
+ return rest;
19
+ }
@@ -0,0 +1,7 @@
1
+ import type { Arm, ParamField } from '../../types/contracts.js';
2
+ import type { SerializedFlagPoint } from '../../types/admin.js';
3
+ export declare function coerceParam(field: ParamField, value: unknown): unknown;
4
+ export declare function paramIssue(field: ParamField, value: unknown): string | null;
5
+ export declare function paramsReadOnly(arm: Arm, editable: boolean): boolean;
6
+ export declare function paramsForArm(arm: Arm, point?: SerializedFlagPoint): Record<string, unknown>;
7
+ export declare function setParam(params: Record<string, unknown>, key: string, field: ParamField, value: unknown): Record<string, unknown>;
@@ -0,0 +1,35 @@
1
+ import { ARMS_COPY } from '../copy/arms.js';
2
+ export function coerceParam(field, value) {
3
+ if (field.type === 'number') {
4
+ if (value === '' || value == null) return undefined;
5
+ return typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
6
+ }
7
+ if (field.type === 'checkbox') return value === true || value === 'true';
8
+ return typeof value === 'string' ? value : '';
9
+ }
10
+ export function paramIssue(field, value) {
11
+ const message = (copy)=>copy.replace('{label}', field.label).replace('{min}', String(field.min)).replace('{max}', String(field.max));
12
+ if (value == null || value === '') return field.required ? message(ARMS_COPY.paramRequired) : null;
13
+ if (field.type === 'number') {
14
+ if (typeof value !== 'number' || !Number.isFinite(value)) return message(ARMS_COPY.paramNumber);
15
+ if (field.min !== undefined && value < field.min) return message(ARMS_COPY.paramMin);
16
+ if (field.max !== undefined && value > field.max) return message(ARMS_COPY.paramMax);
17
+ } else if (field.type === 'checkbox') {
18
+ if (typeof value !== 'boolean') return message(ARMS_COPY.paramCheckbox);
19
+ } else if (field.type === 'select') {
20
+ if (!field.options?.some((option)=>option.value === value)) return message(ARMS_COPY.paramOption);
21
+ } else if (typeof value !== 'string' || field.required && !value.trim()) return message(ARMS_COPY.paramRequired);
22
+ return null;
23
+ }
24
+ export function paramsReadOnly(arm, editable) {
25
+ return !editable || arm.kind !== 'variant';
26
+ }
27
+ export function paramsForArm(arm, point) {
28
+ return arm.kind !== 'variant' && point ? point.effectiveDefaults : arm.params ?? {};
29
+ }
30
+ export function setParam(params, key, field, value) {
31
+ return {
32
+ ...params,
33
+ [key]: coerceParam(field, value)
34
+ };
35
+ }