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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -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 +99 -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 +133 -0
  453. package/dist/types/contracts.js +2 -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 +137 -3
@@ -0,0 +1,237 @@
1
+ import { txExecutor } from '../db/executor.js';
2
+ import { bumpRevision, writeManifest } from '../db/state.js';
3
+ import { XpRefusal } from '../errors.js';
4
+ import { xpOptionsFromReq } from '../options/fromReq.js';
5
+ export async function loadManifestInputs(req, { excludeIds = [], pruneFlagDefaultIds = [] } = {}) {
6
+ // §7.8 manifest reads: transitions compile all live/archived routing state.
7
+ const result = await req.payload.find({
8
+ collection: 'xp-experiments',
9
+ req,
10
+ overrideAccess: true,
11
+ depth: 1,
12
+ limit: 0,
13
+ where: {
14
+ status: {
15
+ in: [
16
+ 'running',
17
+ 'decided',
18
+ 'archived'
19
+ ]
20
+ }
21
+ }
22
+ });
23
+ const excluded = new Set(excludeIds.map(String));
24
+ const experiments = [];
25
+ for (const doc of result.docs){
26
+ if (excluded.has(String(doc.id))) continue;
27
+ // §7.8 manifest reads: archived routing needs all arms from the last test,
28
+ // while active assignment uses the current immutable phase.
29
+ const phases = await req.payload.find({
30
+ collection: 'xp-phases',
31
+ req,
32
+ overrideAccess: true,
33
+ depth: 0,
34
+ limit: 1,
35
+ where: {
36
+ experiment: {
37
+ equals: doc.id
38
+ },
39
+ ...doc.status === 'archived' ? {
40
+ phaseKind: {
41
+ equals: 'test'
42
+ }
43
+ } : {}
44
+ },
45
+ sort: '-phase'
46
+ });
47
+ const rollout = phases.docs[0]?.phaseKind === 'rollout';
48
+ // §7.8 manifest reads: runtime rollout ranges use the test's full weights (including their exact
49
+ // floating-point sum) and its control content. The rollout record has only
50
+ // winner + holdout; never reconstruct the holdout boundary from those weights.
51
+ const test = rollout ? await req.payload.find({
52
+ collection: 'xp-phases',
53
+ req,
54
+ overrideAccess: true,
55
+ depth: 0,
56
+ limit: 1,
57
+ where: {
58
+ experiment: {
59
+ equals: doc.id
60
+ },
61
+ phaseKind: {
62
+ equals: 'test'
63
+ }
64
+ },
65
+ sort: '-phase'
66
+ }) : null;
67
+ if (rollout && !test?.docs[0]) throw new Error('Missing test phase for rollout ' + doc.id);
68
+ experiments.push({
69
+ ...doc,
70
+ currentPhase: doc.status === 'archived' ? null : phases.docs[0] ?? null,
71
+ ...doc.status === 'archived' ? {
72
+ lastTestPhase: phases.docs[0] ?? null
73
+ } : {},
74
+ ...rollout ? {
75
+ lastTestPhase: test.docs[0]
76
+ } : {}
77
+ });
78
+ }
79
+ // §7.8 manifest reads: defaults are part of the private compiled manifest.
80
+ const state = await req.payload.findGlobal({
81
+ slug: 'xp-state',
82
+ req,
83
+ overrideAccess: true,
84
+ depth: 0
85
+ });
86
+ return {
87
+ experiments,
88
+ flagDefaults: state.flagDefaults ?? {},
89
+ excludeIds,
90
+ pruneFlagDefaultIds
91
+ };
92
+ }
93
+ /** The builder has already acquired the state lock with bumpRevision. */ async function pruneDeletedDefaults(req, inputs) {
94
+ if (!inputs.pruneFlagDefaultIds?.length) return;
95
+ const excluded = new Set(inputs.pruneFlagDefaultIds.map(String));
96
+ const defaults = Object.fromEntries(Object.entries(inputs.flagDefaults).filter(([, value])=>!excluded.has(String(value.experiment))));
97
+ if (Object.keys(defaults).length === Object.keys(inputs.flagDefaults).length) return;
98
+ // §7.8 transition write: keep delete exclusions durable under the state
99
+ // lock, so a later unrelated compilation cannot restore deleted defaults.
100
+ await req.payload.updateGlobal({
101
+ slug: 'xp-state',
102
+ req,
103
+ overrideAccess: true,
104
+ data: {
105
+ flagDefaults: defaults
106
+ }
107
+ });
108
+ inputs.flagDefaults = defaults;
109
+ }
110
+ /** Pure compiler. Assignment config comes exclusively from the frozen phase. */ export function compileManifest(inputs, revision, { attributeKeys, onInvalid } = {}) {
111
+ const excluded = new Set(inputs.excludeIds?.map(String));
112
+ const prunedDefaults = new Set(inputs.pruneFlagDefaultIds?.map(String));
113
+ const manifest = {
114
+ v: 1,
115
+ revision,
116
+ experiments: [],
117
+ flagDefaults: {},
118
+ archivedWinners: {},
119
+ archivedPages: {}
120
+ };
121
+ for (const [point, value] of Object.entries(inputs.flagDefaults)){
122
+ if (!prunedDefaults.has(String(value.experiment))) manifest.flagDefaults[point] = structuredClone(value);
123
+ }
124
+ for (const experiment of inputs.experiments){
125
+ if (excluded.has(String(experiment.id))) continue;
126
+ const { decision, currentPhase: phase } = experiment;
127
+ if (experiment.status === 'archived') {
128
+ if (!decision?.winnerArm || decision.discardedAt) continue;
129
+ const testPhase = experiment.lastTestPhase ?? (phase?.phaseKind === 'test' ? phase : null);
130
+ if (!testPhase) throw new XpRefusal('invalid-status', `Missing last test phase for experiment ${experiment.slug}`, undefined, 409);
131
+ const winner = testPhase.arms.find((arm)=>arm.key === decision.winnerArm);
132
+ if (experiment.kind === 'block' && winner?.slot) manifest.archivedWinners[experiment.slug] = winner.slot;
133
+ if (experiment.kind === 'page' && winner?.pageTarget) {
134
+ for (const arm of testPhase.arms){
135
+ if (arm.pageTarget && arm.pageTarget !== winner.pageTarget) {
136
+ manifest.archivedPages[arm.pageTarget] = {
137
+ slug: experiment.slug,
138
+ winnerPath: winner.pageTarget
139
+ };
140
+ }
141
+ }
142
+ }
143
+ continue;
144
+ }
145
+ if (experiment.status !== 'running' && experiment.status !== 'decided') continue;
146
+ if (!phase) throw new Error(`Missing current phase for experiment ${experiment.slug}`);
147
+ const targeting = [];
148
+ for (const rule of phase.targeting ?? []){
149
+ // Legacy phases have no stored type. Only those consult today's registry.
150
+ const type = rule.type ?? (attributeKeys && Object.hasOwn(attributeKeys, rule.attribute) ? attributeKeys[rule.attribute]?.type : undefined);
151
+ if (type !== 'string' && type !== 'number' && type !== 'boolean') break;
152
+ targeting.push({
153
+ attribute: rule.attribute,
154
+ op: rule.op,
155
+ value: rule.value,
156
+ type
157
+ });
158
+ }
159
+ if (targeting.length !== (phase.targeting ?? []).length) {
160
+ onInvalid?.(experiment.slug, 'untyped-targeting-rule');
161
+ continue;
162
+ }
163
+ const rollout = experiment.status === 'decided' && phase.phaseKind === 'rollout' && !phase.endedAt && !decision?.holdoutReleasedAt;
164
+ let layer;
165
+ if ((experiment.status === 'running' || rollout) && experiment.layer != null) {
166
+ if (typeof experiment.layer !== 'object' || typeof experiment.layer.slug !== 'string' || !experiment.layer.slug || !Array.isArray(experiment.layerSlices)) {
167
+ throw new XpRefusal('invalid-status', `Incomplete layer configuration for experiment ${experiment.slug}`, undefined, 409);
168
+ }
169
+ layer = {
170
+ slug: experiment.layer.slug,
171
+ slices: experiment.layerSlices
172
+ };
173
+ }
174
+ const compiled = {
175
+ id: experiment.id,
176
+ slug: experiment.slug,
177
+ kind: experiment.kind,
178
+ unitType: experiment.unitType,
179
+ status: experiment.status,
180
+ phase: phase.phase,
181
+ phaseKind: rollout ? 'rollout' : 'test',
182
+ seed: phase.seed,
183
+ coverage: layer || experiment.status === 'decided' && !rollout ? 1 : phase.allocation / 100,
184
+ arms: (phase.phaseKind === 'rollout' ? experiment.lastTestPhase?.arms ?? phase.arms : phase.arms).map(({ key, kind, weight, slot, pageTarget, params })=>({
185
+ key,
186
+ kind,
187
+ weight,
188
+ ...slot != null ? {
189
+ slot
190
+ } : {},
191
+ ...pageTarget != null ? {
192
+ pageTarget
193
+ } : {},
194
+ ...params != null ? {
195
+ params
196
+ } : {}
197
+ })),
198
+ targeting,
199
+ exposurePolicy: experiment.exposurePolicy,
200
+ deferrable: targeting.some((rule)=>rule.attribute === 'loggedIn' || rule.attribute === 'staff'),
201
+ ...experiment.flagPoint ? {
202
+ flagPoint: experiment.flagPoint
203
+ } : {},
204
+ ...layer ? {
205
+ layer
206
+ } : {},
207
+ ...decision?.winnerArm ? {
208
+ winnerArm: decision.winnerArm
209
+ } : {}
210
+ };
211
+ if (rollout) {
212
+ const holdout = phase.arms.find((arm)=>arm.kind === 'holdout');
213
+ if (holdout) compiled.holdoutKey = holdout.key;
214
+ }
215
+ manifest.experiments.push(structuredClone(compiled));
216
+ }
217
+ return manifest;
218
+ }
219
+ /** Take the state lock before reading inputs. Under READ COMMITTED, a builder
220
+ * waiting for another transition then sees that transition's committed changes,
221
+ * so a later revision cannot overwrite the manifest with pre-lock input state.
222
+ * All reads and writes join the request's transaction.
223
+ */ export async function buildAndStoreManifest(req, options = {}) {
224
+ const exec = txExecutor(req);
225
+ const revision = await bumpRevision(exec);
226
+ const inputs = await loadManifestInputs(req, options);
227
+ await pruneDeletedDefaults(req, inputs);
228
+ const manifest = compileManifest(inputs, revision, {
229
+ attributeKeys: xpOptionsFromReq(req).attributeKeys,
230
+ onInvalid: (slug, reason)=>req.payload.logger.warn({
231
+ slug,
232
+ reason
233
+ }, 'Experiment omitted from manifest')
234
+ });
235
+ await writeManifest(exec, manifest);
236
+ return manifest;
237
+ }
@@ -0,0 +1,5 @@
1
+ import type { Config } from 'payload';
2
+ import type { ResolvedOptions } from './types/contracts.js';
3
+ export declare function createOnInit<TUser extends {
4
+ id: string | number;
5
+ }>(options: ResolvedOptions<TUser>): NonNullable<Config['onInit']>;
package/dist/onInit.js ADDED
@@ -0,0 +1,13 @@
1
+ import { validateDimensions } from './events/dimensions.js';
2
+ import { assertPostgresAdapter, probeTransactions } from './db/adapter.js';
3
+ import { hasHostAttributesOf } from './server/serverRuntime.js';
4
+ export function createOnInit(options) {
5
+ return async (payload)=>{
6
+ validateDimensions(options.events?.dimensions);
7
+ assertPostgresAdapter(payload);
8
+ await probeTransactions(payload);
9
+ if (Object.hasOwn(options.attributeKeys ?? {}, 'staff') && !hasHostAttributesOf(options.runtime)) {
10
+ throw new Error('`staff` is declared in attributeKeys, so pass an `attributesOf` that identifies staff to defineExperimentsServerRuntime');
11
+ }
12
+ };
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { ResolvedOptions } from '../types/contracts.js';
3
+ export declare function xpOptionsFromReq(req: PayloadRequest): ResolvedOptions;
@@ -0,0 +1,5 @@
1
+ export function xpOptionsFromReq(req) {
2
+ const options = req.payload.config.custom?.xp;
3
+ if (!options) throw new Error('Experiments options are missing from req.payload.config.custom.xp; install the experiments plugin first.');
4
+ return options;
5
+ }
@@ -0,0 +1,9 @@
1
+ import type { ExperimentsPluginOptions, ResolvedOptions } from './types/contracts.js';
2
+ /** Resolve once per registration list; every consumer shares the same field defaults. */
3
+ export declare function puckCollections(options: Pick<ExperimentsPluginOptions, 'puck'>): Array<{
4
+ slug: string;
5
+ field: string;
6
+ }>;
7
+ export declare function resolveOptions<TUser extends {
8
+ id: string | number;
9
+ }>(input: ExperimentsPluginOptions<TUser>): ResolvedOptions<TUser>;
@@ -0,0 +1,99 @@
1
+ import { DEFAULT_MAX_BODY_BYTES } from './constants.js';
2
+ const normalisedCollections = new WeakMap();
3
+ /** Resolve once per registration list; every consumer shares the same field defaults. */ export function puckCollections(options) {
4
+ const entries = options.puck?.collections;
5
+ if (!entries) return [];
6
+ const cached = normalisedCollections.get(entries);
7
+ if (cached) return cached;
8
+ const resolved = entries.map((entry)=>typeof entry === 'string' ? {
9
+ slug: entry,
10
+ field: 'puckData'
11
+ } : {
12
+ slug: entry.slug,
13
+ field: entry.field ?? 'puckData'
14
+ });
15
+ normalisedCollections.set(entries, resolved);
16
+ normalisedCollections.set(resolved, resolved);
17
+ return resolved;
18
+ }
19
+ export function resolveOptions(input) {
20
+ if (typeof input?.access?.manage !== 'function') throw new Error('Experiments access.manage must be a function');
21
+ const { manage, admin = manage, readResults = manage, readIdentities = admin } = input.access;
22
+ for (const [name, access] of Object.entries({
23
+ admin,
24
+ readResults,
25
+ readIdentities
26
+ })){
27
+ if (typeof access !== 'function') throw new Error(`Experiments access.${name} must be a function`);
28
+ }
29
+ const ids = new Set();
30
+ for (const point of input.flagPoints ?? []){
31
+ if (typeof point.id !== 'string' || !point.id || ids.has(point.id)) throw new Error('Experiments flag point ids must be unique nonempty strings');
32
+ ids.add(point.id);
33
+ }
34
+ // Record keys are unique by construction; validate each declared key and type.
35
+ for (const [key, attribute] of Object.entries(input.attributeKeys ?? {})){
36
+ if (!key || !attribute || ![
37
+ 'string',
38
+ 'number',
39
+ 'boolean'
40
+ ].includes(attribute.type)) throw new Error(`Invalid experiments attribute key or type: ${key}`);
41
+ if (attribute.values !== undefined && (!Array.isArray(attribute.values) || attribute.values.some((value)=>typeof value !== 'string'))) {
42
+ throw new Error(`Experiments attribute values must be strings: ${key}`);
43
+ }
44
+ }
45
+ for (const name of input.eventSources ?? []){
46
+ if (typeof name !== 'string' || !/^[a-z0-9_]+$/.test(name)) throw new Error('Experiments event source names must match /^[a-z0-9_]+$/');
47
+ }
48
+ const retentionDays = input.events?.retentionDays ?? 180;
49
+ const maxBodyBytes = input.events?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
50
+ for (const [name, value] of Object.entries({
51
+ retentionDays,
52
+ maxBodyBytes
53
+ })){
54
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) throw new Error(`Experiments events.${name} must be a finite positive integer`);
55
+ }
56
+ const dimensions = input.events?.dimensions === undefined ? [] : input.events.dimensions;
57
+ return {
58
+ ...input,
59
+ access: {
60
+ manage,
61
+ admin,
62
+ readResults,
63
+ readIdentities
64
+ },
65
+ flagPoints: [
66
+ ...input.flagPoints ?? []
67
+ ],
68
+ metricSources: [
69
+ ...input.metricSources ?? []
70
+ ],
71
+ eventSources: [
72
+ ...input.eventSources ?? []
73
+ ],
74
+ attributeKeys: Object.fromEntries(Object.entries(input.attributeKeys ?? {}).map(([key, attribute])=>[
75
+ key,
76
+ {
77
+ ...attribute,
78
+ ...attribute.values ? {
79
+ values: [
80
+ ...attribute.values
81
+ ]
82
+ } : {}
83
+ }
84
+ ])),
85
+ events: {
86
+ ...input.events,
87
+ retentionDays,
88
+ maxBodyBytes,
89
+ dimensions: Array.isArray(dimensions) ? [
90
+ ...dimensions
91
+ ] : dimensions
92
+ },
93
+ puck: {
94
+ ...input.puck,
95
+ enabled: input.puck?.enabled ?? Boolean(input.puck?.collections?.length),
96
+ collections: puckCollections(input)
97
+ }
98
+ };
99
+ }
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from 'payload';
2
+ import type { ExperimentsPluginOptions } from './types/contracts.js';
3
+ export declare function createExperimentsPlugin<TUser extends {
4
+ id: string | number;
5
+ } = {
6
+ id: string | number;
7
+ }>(input: ExperimentsPluginOptions<TUser>): Plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,62 @@
1
+ import { buildCollections } from './collections/index.js';
2
+ import { buildEndpoints } from './endpoints/index.js';
3
+ import { contentChangesBeforeHook, contentChangesHook } from './hooks/contentChanges.js';
4
+ import { createOnInit } from './onInit.js';
5
+ import { puckCollections, resolveOptions } from './options.js';
6
+ export function createExperimentsPlugin(input) {
7
+ const options = resolveOptions(input);
8
+ return (config)=>{
9
+ const registrations = puckCollections(options);
10
+ for (const { slug } of registrations){
11
+ if (!config.collections?.some((collection)=>collection.slug === slug)) throw new Error(`Unknown experiments puck collection: ${slug}`);
12
+ }
13
+ // The frozen factories consume registration/access metadata, never TUser callbacks.
14
+ // Erase the user subtype only at this non-generic factory boundary.
15
+ const { collections, globals } = buildCollections(options);
16
+ const onInit = createOnInit(options);
17
+ return {
18
+ ...config,
19
+ collections: [
20
+ ...(config.collections ?? []).map((collection)=>{
21
+ const puck = options.puck?.enabled && registrations.some((entry)=>entry.slug === collection.slug);
22
+ if (!puck) return collection;
23
+ return {
24
+ ...collection,
25
+ hooks: {
26
+ ...collection.hooks,
27
+ beforeChange: [
28
+ ...collection.hooks?.beforeChange ?? [],
29
+ contentChangesBeforeHook
30
+ ],
31
+ ...puck ? {
32
+ afterChange: [
33
+ ...collection.hooks?.afterChange ?? [],
34
+ contentChangesHook
35
+ ]
36
+ } : {}
37
+ }
38
+ };
39
+ }),
40
+ ...collections
41
+ ],
42
+ globals: [
43
+ ...config.globals ?? [],
44
+ ...globals
45
+ ],
46
+ endpoints: [
47
+ ...config.endpoints ?? [],
48
+ ...buildEndpoints({
49
+ options
50
+ })
51
+ ],
52
+ custom: {
53
+ ...config.custom,
54
+ xp: options
55
+ },
56
+ onInit: async (payload)=>{
57
+ await config.onInit?.(payload);
58
+ await onInit(payload);
59
+ }
60
+ };
61
+ };
62
+ }
@@ -0,0 +1,3 @@
1
+ import type { NextResponse } from 'next/server';
2
+ import type { ExperimentsProxyResult } from './resolve.js';
3
+ export declare function applyExperiments(response: NextResponse, result: ExperimentsProxyResult): NextResponse;
@@ -0,0 +1,5 @@
1
+ export function applyExperiments(response, result) {
2
+ for (const cookie of result.setCookies)response.cookies.set(cookie);
3
+ result.responseHeaders?.forEach((value, name)=>response.headers.set(name, value));
4
+ return response;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { type AssignmentResult } from '../runtime/assign.js';
2
+ import type { Attributes } from '../types/contracts.js';
3
+ import type { Manifest } from '../types/manifest.js';
4
+ /** Serve deterministic site content without assigning, hashing or retaining a unit id. */
5
+ export declare function consentFallback(manifest: Manifest, attributes: Attributes, context: Pick<AssignmentResult, 'staff' | 'bot' | 'preview' | 'sessionUnknown' | 'method'>): AssignmentResult;
@@ -0,0 +1,27 @@
1
+ import { fillFlagPoints } from '../runtime/assign.js';
2
+ import { evaluateTargeting } from '../runtime/targeting.js';
3
+ import { controlAssignments } from './control.js';
4
+ /** Serve deterministic site content without assigning, hashing or retaining a unit id. */ export function consentFallback(manifest, attributes, context) {
5
+ const result = controlAssignments(manifest, {
6
+ ...context,
7
+ visitorId: undefined,
8
+ userId: undefined
9
+ });
10
+ const identityFreeAttributes = {
11
+ ...attributes
12
+ };
13
+ delete identityFreeAttributes.userId;
14
+ delete identityFreeAttributes.visitorId;
15
+ if (context.sessionUnknown) {
16
+ delete identityFreeAttributes.loggedIn;
17
+ delete identityFreeAttributes.staff;
18
+ }
19
+ for (const experiment of manifest.experiments){
20
+ if (experiment.status !== 'decided' || experiment.phaseKind === 'rollout') continue;
21
+ const winner = experiment.arms.find((arm)=>arm.key === experiment.winnerArm);
22
+ if (!winner || !evaluateTargeting(experiment.targeting, identityFreeAttributes)) continue;
23
+ result.bySlug[experiment.slug].arm = winner.key;
24
+ }
25
+ fillFlagPoints(result, manifest);
26
+ return result;
27
+ }
@@ -0,0 +1,6 @@
1
+ import { type AssignmentResult } from '../runtime/assign.js';
2
+ import type { Manifest } from '../types/manifest.js';
3
+ /** Explicit control entries prevent deferred user/session assignments from becoming exposable. */
4
+ export declare function controlAssignments(manifest: Manifest, context: Pick<AssignmentResult, 'visitorId' | 'userId' | 'staff' | 'bot' | 'preview' | 'sessionUnknown' | 'method'>): AssignmentResult;
5
+ /** Last resort: do not inspect a manifest that could not even produce controls. */
6
+ export declare function emptyAssignments(context: Pick<AssignmentResult, 'visitorId' | 'userId' | 'staff' | 'bot' | 'preview' | 'sessionUnknown' | 'method'>): AssignmentResult;
@@ -0,0 +1,36 @@
1
+ import { fillFlagPoints } from '../runtime/assign.js';
2
+ /** Explicit control entries prevent deferred user/session assignments from becoming exposable. */ export function controlAssignments(manifest, context) {
3
+ const result = {
4
+ ...context,
5
+ revision: manifest.revision,
6
+ archivedWinners: {
7
+ ...manifest.archivedWinners
8
+ },
9
+ bySlug: Object.create(null),
10
+ flagPoints: Object.create(null),
11
+ deferred: []
12
+ };
13
+ for (const experiment of manifest.experiments){
14
+ result.bySlug[experiment.slug] = {
15
+ slug: experiment.slug,
16
+ experimentId: experiment.id,
17
+ arm: experiment.arms.find((arm)=>arm.kind === 'control')?.key ?? 'control',
18
+ phase: experiment.phase,
19
+ exposable: false,
20
+ policy: experiment.exposurePolicy,
21
+ source: 'fallback'
22
+ };
23
+ }
24
+ fillFlagPoints(result, manifest);
25
+ return result;
26
+ }
27
+ /** Last resort: do not inspect a manifest that could not even produce controls. */ export function emptyAssignments(context) {
28
+ return {
29
+ ...context,
30
+ revision: 0,
31
+ archivedWinners: {},
32
+ bySlug: Object.create(null),
33
+ flagPoints: Object.create(null),
34
+ deferred: []
35
+ };
36
+ }
@@ -0,0 +1,3 @@
1
+ import { NextResponse, type NextRequest } from 'next/server.js';
2
+ import type { ExperimentsProxyRuntime } from '../types/runtime.js';
3
+ export declare function createExperimentProxy(proxyRuntime: ExperimentsProxyRuntime): (request: NextRequest) => Promise<NextResponse>;
@@ -0,0 +1,15 @@
1
+ import { NextResponse } from 'next/server.js';
2
+ import { resolveExperiments } from './resolve.js';
3
+ import { applyExperiments } from './apply.js';
4
+ export function createExperimentProxy(proxyRuntime) {
5
+ return async (request)=>{
6
+ const result = await resolveExperiments(request, proxyRuntime);
7
+ const options = {
8
+ request: {
9
+ headers: result.requestHeaders
10
+ }
11
+ };
12
+ const response = result.rewrite ? NextResponse.rewrite(result.rewrite, options) : NextResponse.next(options);
13
+ return applyExperiments(response, result);
14
+ };
15
+ }
@@ -0,0 +1,4 @@
1
+ import type { AssignmentResult } from '../runtime/assign.js';
2
+ import type { Manifest } from '../types/manifest.js';
3
+ import type { NextRequest } from 'next/server';
4
+ export declare function pageRewrite(manifest: Manifest, assignments: AssignmentResult, requestURL: NextRequest['nextUrl']): URL | undefined;
@@ -0,0 +1,41 @@
1
+ import { findNormalisedPathKey, normalisePath } from '../runtime/normalisePath.js';
2
+ // Manifest targets are local paths. A malformed target must never become an open rewrite.
3
+ function targetURL(path, requestURL) {
4
+ if (!path?.startsWith('/') || path.startsWith('//') || /[\\\r\n?#]/.test(path)) return undefined;
5
+ try {
6
+ const url = requestURL.clone();
7
+ url.pathname = path;
8
+ if (url.origin !== requestURL.origin) return undefined;
9
+ return new URL(url.href);
10
+ } catch {
11
+ return undefined;
12
+ }
13
+ }
14
+ export function pageRewrite(manifest, assignments, requestURL) {
15
+ const experiment = manifest.experiments.find((experiment)=>experiment.kind === 'page' && (experiment.status === 'running' || experiment.status === 'decided') && experiment.arms.some((arm)=>arm.pageTarget !== undefined && normalisePath(arm.pageTarget) === normalisePath(requestURL.pathname)));
16
+ if (experiment) {
17
+ const assignment = assignments.bySlug[experiment.slug];
18
+ const control = experiment.arms.find((arm)=>arm.kind === 'control');
19
+ const arm = experiment.arms.find((arm)=>arm.key === assignment?.arm);
20
+ const target = targetURL((arm?.kind === 'holdout' ? control : arm)?.pageTarget, requestURL);
21
+ if (target) return target;
22
+ if (control) {
23
+ // The proxy has committed to serving control. Deferred server evaluation
24
+ // must not later describe a different, exposable page for this request.
25
+ assignments.deferred = assignments.deferred.filter((slug)=>slug !== experiment.slug);
26
+ assignments.bySlug[experiment.slug] = {
27
+ slug: experiment.slug,
28
+ experimentId: experiment.id,
29
+ arm: control.key,
30
+ phase: experiment.phase,
31
+ exposable: false,
32
+ policy: experiment.exposurePolicy,
33
+ source: 'fallback'
34
+ };
35
+ }
36
+ return targetURL(control?.pageTarget, requestURL);
37
+ }
38
+ const archivedPath = findNormalisedPathKey(manifest.archivedPages, requestURL.pathname);
39
+ const archived = archivedPath === undefined ? undefined : manifest.archivedPages[archivedPath];
40
+ return targetURL(archived?.winnerPath, requestURL);
41
+ }
@@ -0,0 +1,13 @@
1
+ import type { NextRequest, NextResponse } from 'next/server';
2
+ import type { ExperimentsProxyRuntime } from '../types/runtime.js';
3
+ export interface ExperimentsProxyResult {
4
+ responseHeaders?: Headers;
5
+ requestHeaders: Headers;
6
+ requestCookies: Array<{
7
+ name: string;
8
+ value: string;
9
+ }>;
10
+ setCookies: Array<Exclude<Parameters<NextResponse['cookies']['set']>[0], string>>;
11
+ rewrite?: URL;
12
+ }
13
+ export declare function resolveExperiments(request: NextRequest, proxyRuntime: ExperimentsProxyRuntime): Promise<ExperimentsProxyResult>;