@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.
- package/LICENSE +21 -0
- package/README.md +590 -2
- package/dist/access.d.ts +1 -0
- package/dist/access.js +2 -0
- package/dist/admin/AllocationField.d.ts +2 -0
- package/dist/admin/AllocationField.js +80 -0
- package/dist/admin/FlagPointSelect.d.ts +2 -0
- package/dist/admin/FlagPointSelect.js +54 -0
- package/dist/admin/LayerUsage.d.ts +2 -0
- package/dist/admin/LayerUsage.js +24 -0
- package/dist/admin/LockedFieldNotice.d.ts +2 -0
- package/dist/admin/LockedFieldNotice.js +26 -0
- package/dist/admin/MetricSourceSelect.d.ts +2 -0
- package/dist/admin/MetricSourceSelect.js +32 -0
- package/dist/admin/PhaseHistory.d.ts +2 -0
- package/dist/admin/PhaseHistory.js +76 -0
- package/dist/admin/PreviewLinks.d.ts +11 -0
- package/dist/admin/PreviewLinks.js +69 -0
- package/dist/admin/StatusCell.d.ts +3 -0
- package/dist/admin/StatusCell.js +19 -0
- package/dist/admin/SummaryCell.d.ts +2 -0
- package/dist/admin/SummaryCell.js +32 -0
- package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
- package/dist/admin/actions/AddMetricDialog.js +138 -0
- package/dist/admin/actions/AllocateDialog.d.ts +6 -0
- package/dist/admin/actions/AllocateDialog.js +81 -0
- package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
- package/dist/admin/actions/ConfirmDialog.js +192 -0
- package/dist/admin/actions/DecideDialog.d.ts +7 -0
- package/dist/admin/actions/DecideDialog.js +173 -0
- package/dist/admin/actions/ExperimentActions.d.ts +3 -0
- package/dist/admin/actions/ExperimentActions.js +110 -0
- package/dist/admin/actions/MakeContentResult.d.ts +6 -0
- package/dist/admin/actions/MakeContentResult.js +50 -0
- package/dist/admin/actions/actions.model.d.ts +37 -0
- package/dist/admin/actions/actions.model.js +71 -0
- package/dist/admin/actions/decide.model.d.ts +21 -0
- package/dist/admin/actions/decide.model.js +38 -0
- package/dist/admin/allocation.model.d.ts +20 -0
- package/dist/admin/allocation.model.js +38 -0
- package/dist/admin/api.d.ts +32 -0
- package/dist/admin/api.js +95 -0
- package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
- package/dist/admin/armSelect/ArmSelectField.js +185 -0
- package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
- package/dist/admin/armSelect/armSelect.model.js +20 -0
- package/dist/admin/arms/ArmsField.d.ts +2 -0
- package/dist/admin/arms/ArmsField.js +310 -0
- package/dist/admin/arms/ParamsEditor.d.ts +16 -0
- package/dist/admin/arms/ParamsEditor.js +79 -0
- package/dist/admin/arms/arms.model.d.ts +21 -0
- package/dist/admin/arms/arms.model.js +153 -0
- package/dist/admin/arms/pageTarget.model.d.ts +4 -0
- package/dist/admin/arms/pageTarget.model.js +19 -0
- package/dist/admin/arms/params.model.d.ts +7 -0
- package/dist/admin/arms/params.model.js +35 -0
- package/dist/admin/copy/actions.d.ts +38 -0
- package/dist/admin/copy/actions.js +38 -0
- package/dist/admin/copy/armSelect.d.ts +11 -0
- package/dist/admin/copy/armSelect.js +11 -0
- package/dist/admin/copy/arms.d.ts +37 -0
- package/dist/admin/copy/arms.js +47 -0
- package/dist/admin/copy/common.d.ts +20 -0
- package/dist/admin/copy/common.js +27 -0
- package/dist/admin/copy/history.d.ts +9 -0
- package/dist/admin/copy/history.js +9 -0
- package/dist/admin/copy/metrics.d.ts +9 -0
- package/dist/admin/copy/metrics.js +9 -0
- package/dist/admin/copy/results.d.ts +78 -0
- package/dist/admin/copy/results.js +81 -0
- package/dist/admin/copy/review.d.ts +33 -0
- package/dist/admin/copy/review.js +127 -0
- package/dist/admin/copy/status.d.ts +8 -0
- package/dist/admin/copy/status.js +8 -0
- package/dist/admin/copy/tabs.d.ts +7 -0
- package/dist/admin/copy/tabs.js +7 -0
- package/dist/admin/copy/who.d.ts +36 -0
- package/dist/admin/copy/who.js +36 -0
- package/dist/admin/labels.d.ts +45 -0
- package/dist/admin/labels.js +53 -0
- package/dist/admin/layerUsage.model.d.ts +15 -0
- package/dist/admin/layerUsage.model.js +18 -0
- package/dist/admin/lockedNotice.model.d.ts +3 -0
- package/dist/admin/lockedNotice.model.js +18 -0
- package/dist/admin/metricSource.model.d.ts +7 -0
- package/dist/admin/metricSource.model.js +11 -0
- package/dist/admin/paths.d.ts +16 -0
- package/dist/admin/paths.js +16 -0
- package/dist/admin/permissions.model.d.ts +10 -0
- package/dist/admin/permissions.model.js +4 -0
- package/dist/admin/phaseHistory.model.d.ts +19 -0
- package/dist/admin/phaseHistory.model.js +19 -0
- package/dist/admin/reload.d.ts +1 -0
- package/dist/admin/reload.js +1 -0
- package/dist/admin/results/ArmTable.d.ts +8 -0
- package/dist/admin/results/ArmTable.js +112 -0
- package/dist/admin/results/CappedCell.d.ts +4 -0
- package/dist/admin/results/CappedCell.js +24 -0
- package/dist/admin/results/CiChart.d.ts +10 -0
- package/dist/admin/results/CiChart.js +178 -0
- package/dist/admin/results/DataQualityPanel.d.ts +4 -0
- package/dist/admin/results/DataQualityPanel.js +40 -0
- package/dist/admin/results/ResultsView.d.ts +3 -0
- package/dist/admin/results/ResultsView.js +312 -0
- package/dist/admin/results/VerdictCard.d.ts +9 -0
- package/dist/admin/results/VerdictCard.js +49 -0
- package/dist/admin/results/chart.model.d.ts +32 -0
- package/dist/admin/results/chart.model.js +52 -0
- package/dist/admin/results/format.d.ts +10 -0
- package/dist/admin/results/format.js +55 -0
- package/dist/admin/results/results.model.d.ts +105 -0
- package/dist/admin/results/results.model.js +175 -0
- package/dist/admin/review/ReviewPanel.d.ts +2 -0
- package/dist/admin/review/ReviewPanel.js +268 -0
- package/dist/admin/review/review.model.d.ts +31 -0
- package/dist/admin/review/review.model.js +40 -0
- package/dist/admin/status.model.d.ts +16 -0
- package/dist/admin/status.model.js +17 -0
- package/dist/admin/styles.css +10 -0
- package/dist/admin/styles.d.ts +1 -0
- package/dist/admin/styles.js +2 -0
- package/dist/admin/summaryBatcher.d.ts +8 -0
- package/dist/admin/summaryBatcher.js +33 -0
- package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
- package/dist/admin/targeting/TargetingAttributeField.js +31 -0
- package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
- package/dist/admin/targeting/TargetingOpField.js +45 -0
- package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
- package/dist/admin/targeting/TargetingValueField.js +142 -0
- package/dist/admin/targeting/targeting.model.d.ts +18 -0
- package/dist/admin/targeting/targeting.model.js +48 -0
- package/dist/admin/useApi.d.ts +20 -0
- package/dist/admin/useApi.js +10 -0
- package/dist/admin/useLayerUsage.d.ts +15 -0
- package/dist/admin/useLayerUsage.js +92 -0
- package/dist/client/ExperimentPageview.d.ts +1 -0
- package/dist/client/ExperimentPageview.js +27 -0
- package/dist/client/ExperimentsProvider.d.ts +6 -0
- package/dist/client/ExperimentsProvider.js +22 -0
- package/dist/client/ViewportExposure.d.ts +9 -0
- package/dist/client/ViewportExposure.js +69 -0
- package/dist/client/beacon.d.ts +17 -0
- package/dist/client/beacon.js +71 -0
- package/dist/client/context.d.ts +2 -0
- package/dist/client/context.js +2 -0
- package/dist/client/idempotency.d.ts +2 -0
- package/dist/client/idempotency.js +13 -0
- package/dist/client/trackConversionBeacon.d.ts +3 -0
- package/dist/client/trackConversionBeacon.js +16 -0
- package/dist/client/useExperiment.d.ts +5 -0
- package/dist/client/useExperiment.js +27 -0
- package/dist/client/useFlagPoint.d.ts +9 -0
- package/dist/client/useFlagPoint.js +32 -0
- package/dist/collections/access.d.ts +21 -0
- package/dist/collections/access.js +25 -0
- package/dist/collections/events.d.ts +3 -0
- package/dist/collections/events.js +117 -0
- package/dist/collections/experiments/armInvariants.d.ts +12 -0
- package/dist/collections/experiments/armInvariants.js +89 -0
- package/dist/collections/experiments/config.d.ts +3 -0
- package/dist/collections/experiments/config.js +574 -0
- package/dist/collections/experiments/createDefaults.d.ts +2 -0
- package/dist/collections/experiments/createDefaults.js +7 -0
- package/dist/collections/experiments/deleteGuard.d.ts +3 -0
- package/dist/collections/experiments/deleteGuard.js +8 -0
- package/dist/collections/experiments/editability.d.ts +4 -0
- package/dist/collections/experiments/editability.js +35 -0
- package/dist/collections/experiments/flagControlParams.d.ts +2 -0
- package/dist/collections/experiments/flagControlParams.js +32 -0
- package/dist/collections/experiments/lockHook.d.ts +2 -0
- package/dist/collections/experiments/lockHook.js +134 -0
- package/dist/collections/identities.d.ts +3 -0
- package/dist/collections/identities.js +30 -0
- package/dist/collections/index.d.ts +6 -0
- package/dist/collections/index.js +24 -0
- package/dist/collections/layers.d.ts +3 -0
- package/dist/collections/layers.js +77 -0
- package/dist/collections/metrics.d.ts +3 -0
- package/dist/collections/metrics.js +115 -0
- package/dist/collections/phases.d.ts +3 -0
- package/dist/collections/phases.js +59 -0
- package/dist/collections/serialize.d.ts +5 -0
- package/dist/collections/serialize.js +40 -0
- package/dist/collections/snapshots.d.ts +3 -0
- package/dist/collections/snapshots.js +47 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/db/adapter.d.ts +3 -0
- package/dist/db/adapter.js +9 -0
- package/dist/db/appends.d.ts +58 -0
- package/dist/db/appends.js +116 -0
- package/dist/db/delete.d.ts +17 -0
- package/dist/db/delete.js +35 -0
- package/dist/db/erase.d.ts +12 -0
- package/dist/db/erase.js +55 -0
- package/dist/db/events.d.ts +21 -0
- package/dist/db/events.js +100 -0
- package/dist/db/executor.d.ts +10 -0
- package/dist/db/executor.js +37 -0
- package/dist/db/identities.d.ts +11 -0
- package/dist/db/identities.js +29 -0
- package/dist/db/locks.d.ts +15 -0
- package/dist/db/locks.js +23 -0
- package/dist/db/manifest.d.ts +8 -0
- package/dist/db/manifest.js +8 -0
- package/dist/db/names.d.ts +271 -0
- package/dist/db/names.js +270 -0
- package/dist/db/pgExecutor.d.ts +41 -0
- package/dist/db/pgExecutor.js +250 -0
- package/dist/db/results.d.ts +53 -0
- package/dist/db/results.js +103 -0
- package/dist/db/slices.d.ts +18 -0
- package/dist/db/slices.js +51 -0
- package/dist/db/snapshots.d.ts +47 -0
- package/dist/db/snapshots.js +89 -0
- package/dist/db/sql.d.ts +5 -0
- package/dist/db/sql.js +5 -0
- package/dist/db/state.d.ts +12 -0
- package/dist/db/state.js +27 -0
- package/dist/db/sweep.d.ts +13 -0
- package/dist/db/sweep.js +90 -0
- package/dist/db/txid.d.ts +7 -0
- package/dist/db/txid.js +12 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +6 -0
- package/dist/endpoints/events.d.ts +7 -0
- package/dist/endpoints/events.js +98 -0
- package/dist/endpoints/index.d.ts +18 -0
- package/dist/endpoints/index.js +160 -0
- package/dist/endpoints/manifest.d.ts +7 -0
- package/dist/endpoints/manifest.js +30 -0
- package/dist/endpoints/preflight.d.ts +8 -0
- package/dist/endpoints/preflight.js +25 -0
- package/dist/endpoints/previewToken.d.ts +7 -0
- package/dist/endpoints/previewToken.js +54 -0
- package/dist/endpoints/recompute.d.ts +7 -0
- package/dist/endpoints/recompute.js +41 -0
- package/dist/endpoints/results.d.ts +24 -0
- package/dist/endpoints/results.js +136 -0
- package/dist/endpoints/summary.d.ts +7 -0
- package/dist/endpoints/summary.js +40 -0
- package/dist/endpoints/transitions.d.ts +42 -0
- package/dist/endpoints/transitions.js +154 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +33 -0
- package/dist/events/body.d.ts +7 -0
- package/dist/events/body.js +43 -0
- package/dist/events/dimensions.d.ts +4 -0
- package/dist/events/dimensions.js +51 -0
- package/dist/events/preview.d.ts +2 -0
- package/dist/events/preview.js +9 -0
- package/dist/events/request.d.ts +3 -0
- package/dist/events/request.js +14 -0
- package/dist/events/rows.d.ts +7 -0
- package/dist/events/rows.js +65 -0
- package/dist/exports/adminClient.d.ts +16 -0
- package/dist/exports/adminClient.js +17 -0
- package/dist/exports/client.d.ts +7 -0
- package/dist/exports/client.js +7 -0
- package/dist/exports/index.d.ts +4 -0
- package/dist/exports/index.js +3 -0
- package/dist/exports/proxy.d.ts +4 -0
- package/dist/exports/proxy.js +3 -0
- package/dist/exports/puck.d.ts +4 -0
- package/dist/exports/puck.js +3 -0
- package/dist/exports/puckEditor.d.ts +1 -0
- package/dist/exports/puckEditor.js +2 -0
- package/dist/exports/rsc.d.ts +13 -0
- package/dist/exports/rsc.js +13 -0
- package/dist/exports/runtimeProxy.d.ts +4 -0
- package/dist/exports/runtimeProxy.js +2 -0
- package/dist/exports/runtimeServer.d.ts +2 -0
- package/dist/exports/runtimeServer.js +1 -0
- package/dist/fields/armSelect.d.ts +7 -0
- package/dist/fields/armSelect.js +33 -0
- package/dist/globals/state.d.ts +3 -0
- package/dist/globals/state.js +31 -0
- package/dist/hooks/contentChanges.d.ts +3 -0
- package/dist/hooks/contentChanges.js +10 -0
- package/dist/hooks/hostHook.d.ts +5 -0
- package/dist/hooks/hostHook.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/manifest/cache.d.ts +10 -0
- package/dist/manifest/cache.js +51 -0
- package/dist/manifest/compile.d.ts +63 -0
- package/dist/manifest/compile.js +237 -0
- package/dist/onInit.d.ts +5 -0
- package/dist/onInit.js +13 -0
- package/dist/options/fromReq.d.ts +3 -0
- package/dist/options/fromReq.js +5 -0
- package/dist/options.d.ts +9 -0
- package/dist/options.js +107 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +62 -0
- package/dist/proxy/apply.d.ts +3 -0
- package/dist/proxy/apply.js +5 -0
- package/dist/proxy/consentFallback.d.ts +5 -0
- package/dist/proxy/consentFallback.js +27 -0
- package/dist/proxy/control.d.ts +6 -0
- package/dist/proxy/control.js +36 -0
- package/dist/proxy/createProxy.d.ts +3 -0
- package/dist/proxy/createProxy.js +15 -0
- package/dist/proxy/pageRewrite.d.ts +4 -0
- package/dist/proxy/pageRewrite.js +41 -0
- package/dist/proxy/resolve.d.ts +13 -0
- package/dist/proxy/resolve.js +163 -0
- package/dist/proxy/skip.d.ts +1 -0
- package/dist/proxy/skip.js +9 -0
- package/dist/puck/ExperimentPicker.d.ts +5 -0
- package/dist/puck/ExperimentPicker.js +88 -0
- package/dist/puck/ExperimentSlot.d.ts +3 -0
- package/dist/puck/ExperimentSlot.js +16 -0
- package/dist/puck/copy.d.ts +14 -0
- package/dist/puck/copy.js +14 -0
- package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
- package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
- package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
- package/dist/puck/editor/cloneSlot.d.ts +3 -0
- package/dist/puck/editor/cloneSlot.js +41 -0
- package/dist/puck/editor/copy.d.ts +25 -0
- package/dist/puck/editor/copy.js +25 -0
- package/dist/puck/editor/editor.model.d.ts +16 -0
- package/dist/puck/editor/editor.model.js +33 -0
- package/dist/puck/experimentSlotField.d.ts +5 -0
- package/dist/puck/experimentSlotField.js +13 -0
- package/dist/puck/slot.model.d.ts +2 -0
- package/dist/puck/slot.model.js +11 -0
- package/dist/puck/slotConfig.d.ts +5 -0
- package/dist/puck/slotConfig.js +25 -0
- package/dist/results/aggregate.d.ts +34 -0
- package/dist/results/aggregate.js +66 -0
- package/dist/results/analyse.d.ts +79 -0
- package/dist/results/analyse.js +378 -0
- package/dist/results/cuped.d.ts +16 -0
- package/dist/results/cuped.js +31 -0
- package/dist/results/decideGuard.d.ts +3 -0
- package/dist/results/decideGuard.js +8 -0
- package/dist/results/guardrails.d.ts +17 -0
- package/dist/results/guardrails.js +10 -0
- package/dist/results/history.d.ts +13 -0
- package/dist/results/history.js +39 -0
- package/dist/results/identityCredit.d.ts +16 -0
- package/dist/results/identityCredit.js +46 -0
- package/dist/results/power.d.ts +15 -0
- package/dist/results/power.js +60 -0
- package/dist/results/rolloutUnits.d.ts +17 -0
- package/dist/results/rolloutUnits.js +26 -0
- package/dist/results/snapshot.d.ts +9 -0
- package/dist/results/snapshot.js +154 -0
- package/dist/results/srm.d.ts +9 -0
- package/dist/results/srm.js +22 -0
- package/dist/results/verdict.d.ts +82 -0
- package/dist/results/verdict.js +85 -0
- package/dist/runtime/assign.d.ts +23 -0
- package/dist/runtime/assign.js +104 -0
- package/dist/runtime/bot.d.ts +1 -0
- package/dist/runtime/bot.js +7 -0
- package/dist/runtime/cookie.d.ts +17 -0
- package/dist/runtime/cookie.js +35 -0
- package/dist/runtime/eligibility.d.ts +22 -0
- package/dist/runtime/eligibility.js +20 -0
- package/dist/runtime/header.d.ts +17 -0
- package/dist/runtime/header.js +97 -0
- package/dist/runtime/loaders.d.ts +17 -0
- package/dist/runtime/loaders.js +29 -0
- package/dist/runtime/normalisePath.d.ts +7 -0
- package/dist/runtime/normalisePath.js +15 -0
- package/dist/runtime/previewToken.d.ts +9 -0
- package/dist/runtime/previewToken.js +23 -0
- package/dist/runtime/proxyRuntime.d.ts +2 -0
- package/dist/runtime/proxyRuntime.js +22 -0
- package/dist/runtime/seed.d.ts +2 -0
- package/dist/runtime/seed.js +4 -0
- package/dist/runtime/signing.d.ts +3 -0
- package/dist/runtime/signing.js +16 -0
- package/dist/runtime/targeting.d.ts +6 -0
- package/dist/runtime/targeting.js +9 -0
- package/dist/server/context.d.ts +10 -0
- package/dist/server/context.js +59 -0
- package/dist/server/eraseUser.d.ts +7 -0
- package/dist/server/eraseUser.js +4 -0
- package/dist/server/getAssignments.d.ts +9 -0
- package/dist/server/getAssignments.js +227 -0
- package/dist/server/getFlagPoint.d.ts +11 -0
- package/dist/server/getFlagPoint.js +36 -0
- package/dist/server/headerRevision.d.ts +6 -0
- package/dist/server/headerRevision.js +10 -0
- package/dist/server/linkIdentity.d.ts +9 -0
- package/dist/server/linkIdentity.js +34 -0
- package/dist/server/metadata.d.ts +7 -0
- package/dist/server/metadata.js +24 -0
- package/dist/server/previewDraft.d.ts +7 -0
- package/dist/server/previewDraft.js +51 -0
- package/dist/server/recordContentChange.d.ts +7 -0
- package/dist/server/recordContentChange.js +150 -0
- package/dist/server/recordExposures.d.ts +7 -0
- package/dist/server/recordExposures.js +56 -0
- package/dist/server/request.d.ts +7 -0
- package/dist/server/request.js +39 -0
- package/dist/server/resolveExperimentData.d.ts +6 -0
- package/dist/server/resolveExperimentData.js +64 -0
- package/dist/server/runResults.d.ts +22 -0
- package/dist/server/runResults.js +307 -0
- package/dist/server/serverRuntime.d.ts +7 -0
- package/dist/server/serverRuntime.js +20 -0
- package/dist/server/sitemap.d.ts +3 -0
- package/dist/server/sitemap.js +6 -0
- package/dist/server/toClientAssignments.d.ts +7 -0
- package/dist/server/toClientAssignments.js +56 -0
- package/dist/server/trackConversion.d.ts +12 -0
- package/dist/server/trackConversion.js +69 -0
- package/dist/targeting/ops.d.ts +8 -0
- package/dist/targeting/ops.js +58 -0
- package/dist/transitions/addMetric.d.ts +16 -0
- package/dist/transitions/addMetric.js +126 -0
- package/dist/transitions/allocate.d.ts +9 -0
- package/dist/transitions/allocate.js +84 -0
- package/dist/transitions/archive.d.ts +9 -0
- package/dist/transitions/archive.js +46 -0
- package/dist/transitions/checkStart.d.ts +61 -0
- package/dist/transitions/checkStart.js +104 -0
- package/dist/transitions/confirmPhrases.d.ts +5 -0
- package/dist/transitions/confirmPhrases.js +18 -0
- package/dist/transitions/decide.d.ts +9 -0
- package/dist/transitions/decide.js +138 -0
- package/dist/transitions/deleteExperiment.d.ts +5 -0
- package/dist/transitions/deleteExperiment.js +66 -0
- package/dist/transitions/kernel.d.ts +31 -0
- package/dist/transitions/kernel.js +69 -0
- package/dist/transitions/makeContent.d.ts +20 -0
- package/dist/transitions/makeContent.js +411 -0
- package/dist/transitions/phaseRecord.d.ts +27 -0
- package/dist/transitions/phaseRecord.js +81 -0
- package/dist/transitions/placements.d.ts +26 -0
- package/dist/transitions/placements.js +89 -0
- package/dist/transitions/preflightData.d.ts +21 -0
- package/dist/transitions/preflightData.js +317 -0
- package/dist/transitions/releaseHoldout.d.ts +8 -0
- package/dist/transitions/releaseHoldout.js +30 -0
- package/dist/transitions/resolveCaps.d.ts +28 -0
- package/dist/transitions/resolveCaps.js +68 -0
- package/dist/transitions/start.d.ts +26 -0
- package/dist/transitions/start.js +220 -0
- package/dist/transitions/stop.d.ts +8 -0
- package/dist/transitions/stop.js +25 -0
- package/dist/types/admin.d.ts +20 -0
- package/dist/types/admin.js +1 -0
- package/dist/types/assignments.d.ts +57 -0
- package/dist/types/assignments.js +1 -0
- package/dist/types/contracts.d.ts +141 -0
- package/dist/types/contracts.js +1 -0
- package/dist/types/endpoints.d.ts +135 -0
- package/dist/types/endpoints.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +8 -0
- package/dist/types/manifest.d.ts +42 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/puck.d.ts +23 -0
- package/dist/types/puck.js +1 -0
- package/dist/types/runtime.d.ts +59 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/snapshot.d.ts +117 -0
- package/dist/types/snapshot.js +1 -0
- package/package.json +138 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
4
|
+
import { registerApiRoute, unregisterApiRoute } from './beacon.js';
|
|
5
|
+
import { ExperimentsContext } from './context.js';
|
|
6
|
+
export function ExperimentsProvider(props) {
|
|
7
|
+
const token = useRef(Symbol()).current;
|
|
8
|
+
// Register committed providers before descendants can send from passive effects.
|
|
9
|
+
useLayoutEffect(()=>{
|
|
10
|
+
registerApiRoute(token, props.value.apiRoute);
|
|
11
|
+
}, [
|
|
12
|
+
token,
|
|
13
|
+
props.value.apiRoute
|
|
14
|
+
]);
|
|
15
|
+
useLayoutEffect(()=>()=>unregisterApiRoute(token), [
|
|
16
|
+
token
|
|
17
|
+
]);
|
|
18
|
+
return /*#__PURE__*/ _jsx(ExperimentsContext.Provider, {
|
|
19
|
+
value: props.value,
|
|
20
|
+
children: props.children
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useContext, useEffect, useRef } from 'react';
|
|
4
|
+
import { sendExposure, warnOnce } from './beacon.js';
|
|
5
|
+
import { ExperimentsContext } from './context.js';
|
|
6
|
+
export function ViewportExposure({ expose, children }) {
|
|
7
|
+
const value = useContext(ExperimentsContext);
|
|
8
|
+
const sentinel = useRef(null);
|
|
9
|
+
const apiRoute = value?.apiRoute;
|
|
10
|
+
const slug = expose?.slug, arm = expose?.arm, phase = expose?.phase;
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
if (slug === undefined || arm === undefined || phase === undefined) return;
|
|
13
|
+
if (apiRoute === undefined) {
|
|
14
|
+
warnOnce('viewport:no-provider', 'ViewportExposure requires ExperimentsProvider to send an exposure.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const target = sentinel.current;
|
|
18
|
+
if (!target || typeof IntersectionObserver === 'undefined') return;
|
|
19
|
+
const observer = new IntersectionObserver((entries)=>{
|
|
20
|
+
if (!entries.some((entry)=>entry.target === target && entry.intersectionRatio >= 0.5)) return;
|
|
21
|
+
sendExposure(apiRoute, {
|
|
22
|
+
type: 'exposure',
|
|
23
|
+
experiment: slug,
|
|
24
|
+
phase,
|
|
25
|
+
arm,
|
|
26
|
+
policy: 'viewport'
|
|
27
|
+
});
|
|
28
|
+
observer.disconnect();
|
|
29
|
+
}, {
|
|
30
|
+
threshold: 0.5
|
|
31
|
+
});
|
|
32
|
+
observer.observe(target);
|
|
33
|
+
return ()=>observer.disconnect();
|
|
34
|
+
}, [
|
|
35
|
+
slug,
|
|
36
|
+
arm,
|
|
37
|
+
phase,
|
|
38
|
+
apiRoute
|
|
39
|
+
]);
|
|
40
|
+
// Nothing to expose (no running experiment, or a render-policy slot): render exactly
|
|
41
|
+
// what experiments-off renders — no wrapper, no sentinel (owner rule O9). Hooks stay above.
|
|
42
|
+
if (expose === undefined) return /*#__PURE__*/ _jsx(_Fragment, {
|
|
43
|
+
children: children
|
|
44
|
+
});
|
|
45
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
46
|
+
"data-xp-viewport": true,
|
|
47
|
+
style: {
|
|
48
|
+
position: 'relative',
|
|
49
|
+
display: 'block'
|
|
50
|
+
},
|
|
51
|
+
children: [
|
|
52
|
+
/*#__PURE__*/ _jsx("div", {
|
|
53
|
+
ref: sentinel,
|
|
54
|
+
"data-xp-sentinel": true,
|
|
55
|
+
"aria-hidden": true,
|
|
56
|
+
style: {
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
top: 0,
|
|
59
|
+
left: 0,
|
|
60
|
+
width: '100%',
|
|
61
|
+
height: '100px',
|
|
62
|
+
pointerEvents: 'none',
|
|
63
|
+
visibility: 'hidden'
|
|
64
|
+
}
|
|
65
|
+
}),
|
|
66
|
+
children
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EventBody } from '../types/endpoints.js';
|
|
2
|
+
type Event = EventBody['events'][number];
|
|
3
|
+
type Exposure = Extract<Event, {
|
|
4
|
+
type: 'exposure';
|
|
5
|
+
}>;
|
|
6
|
+
/** Register only committed providers; the latest mounted provider supplies conversion routing. */
|
|
7
|
+
export declare function registerApiRoute(token: symbol, apiRoute: string): void;
|
|
8
|
+
export declare function unregisterApiRoute(token: symbol): void;
|
|
9
|
+
/** No mounted provider means experiments are not wired on this page: conversions are not sent (spec §13). */
|
|
10
|
+
export declare function conversionApiRoute(): string | undefined;
|
|
11
|
+
export declare function warnOnce(key: string, message: string): void;
|
|
12
|
+
/** Telemetry must never interrupt rendering or a host interaction. */
|
|
13
|
+
export declare function sendEvent(apiRoute: string, event: Event): void;
|
|
14
|
+
export declare function sendExposure(apiRoute: string, event: Exposure, pathname?: string): void;
|
|
15
|
+
/** Internal test seam; deliberately not exported by /client. */
|
|
16
|
+
export declare function __resetBeaconsForTests(): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
const exposures = new Set();
|
|
3
|
+
const pageviews = new Set();
|
|
4
|
+
const warnings = new Set();
|
|
5
|
+
const providers = new Map();
|
|
6
|
+
/** Register only committed providers; the latest mounted provider supplies conversion routing. */ export function registerApiRoute(token, apiRoute) {
|
|
7
|
+
// Updating an existing token preserves its original mount order.
|
|
8
|
+
providers.set(token, apiRoute);
|
|
9
|
+
}
|
|
10
|
+
export function unregisterApiRoute(token) {
|
|
11
|
+
providers.delete(token);
|
|
12
|
+
}
|
|
13
|
+
/** No mounted provider means experiments are not wired on this page: conversions are not sent (spec §13). */ export function conversionApiRoute() {
|
|
14
|
+
return [
|
|
15
|
+
...providers.values()
|
|
16
|
+
].at(-1);
|
|
17
|
+
}
|
|
18
|
+
export function warnOnce(key, message) {
|
|
19
|
+
if (warnings.has(key)) return;
|
|
20
|
+
warnings.add(key);
|
|
21
|
+
console.error(message);
|
|
22
|
+
}
|
|
23
|
+
/** Telemetry must never interrupt rendering or a host interaction. */ export function sendEvent(apiRoute, event) {
|
|
24
|
+
try {
|
|
25
|
+
const body = {
|
|
26
|
+
events: [
|
|
27
|
+
event
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
const json = JSON.stringify(body);
|
|
31
|
+
const url = `${apiRoute}/xp/events`;
|
|
32
|
+
try {
|
|
33
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function' && navigator.sendBeacon(url, new Blob([
|
|
34
|
+
json
|
|
35
|
+
], {
|
|
36
|
+
type: 'text/plain;charset=UTF-8'
|
|
37
|
+
}))) return;
|
|
38
|
+
} catch {}
|
|
39
|
+
try {
|
|
40
|
+
void fetch(url, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
keepalive: true,
|
|
43
|
+
credentials: 'same-origin',
|
|
44
|
+
headers: {
|
|
45
|
+
'content-type': 'text/plain;charset=UTF-8'
|
|
46
|
+
},
|
|
47
|
+
body: json
|
|
48
|
+
}).catch(()=>{});
|
|
49
|
+
} catch {}
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
export function sendExposure(apiRoute, event, pathname) {
|
|
53
|
+
const seen = pathname === undefined ? exposures : pageviews;
|
|
54
|
+
const key = JSON.stringify([
|
|
55
|
+
event.experiment,
|
|
56
|
+
event.phase,
|
|
57
|
+
event.arm,
|
|
58
|
+
...pathname === undefined ? [] : [
|
|
59
|
+
pathname
|
|
60
|
+
]
|
|
61
|
+
]);
|
|
62
|
+
if (seen.has(key)) return;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
sendEvent(apiRoute, event);
|
|
65
|
+
}
|
|
66
|
+
/** Internal test seam; deliberately not exported by /client. */ export function __resetBeaconsForTests() {
|
|
67
|
+
exposures.clear();
|
|
68
|
+
pageviews.clear();
|
|
69
|
+
warnings.clear();
|
|
70
|
+
providers.clear();
|
|
71
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/** Prefer a caller's stable key; restricted browser crypto must not break the caller. */ export function idempotencyKey(provided) {
|
|
3
|
+
// An empty key would collapse every conversion of this event for the unit into one dedupe row.
|
|
4
|
+
if (provided) return provided;
|
|
5
|
+
try {
|
|
6
|
+
if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();
|
|
7
|
+
} catch {}
|
|
8
|
+
try {
|
|
9
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
10
|
+
return Array.from(bytes, (byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
11
|
+
} catch {}
|
|
12
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { conversionApiRoute, sendEvent, warnOnce } from './beacon.js';
|
|
3
|
+
import { idempotencyKey } from './idempotency.js';
|
|
4
|
+
export function trackConversionBeacon(eventName, opts) {
|
|
5
|
+
const apiRoute = conversionApiRoute();
|
|
6
|
+
if (apiRoute === undefined) {
|
|
7
|
+
// Without an ExperimentsProvider the host has experiments disabled here; never send to a guessed route.
|
|
8
|
+
if (process.env.NODE_ENV !== 'production') warnOnce('conversion:no-provider', `trackConversionBeacon('${eventName}') was called without an ExperimentsProvider; nothing was sent.`);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
sendEvent(apiRoute, {
|
|
12
|
+
type: 'conversion',
|
|
13
|
+
eventName,
|
|
14
|
+
idempotencyKey: idempotencyKey(opts?.idempotencyKey)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { sendExposure, warnOnce } from './beacon.js';
|
|
4
|
+
import { ExperimentsContext } from './context.js';
|
|
5
|
+
export function useExperiment(slug) {
|
|
6
|
+
const value = useContext(ExperimentsContext);
|
|
7
|
+
const entry = value && Object.hasOwn(value.experiments, slug) ? value.experiments[slug] : undefined;
|
|
8
|
+
if (!entry) {
|
|
9
|
+
const message = `Experiment "${slug}" must be allow-listed in toClientAssignments and rendered inside ExperimentsProvider.`;
|
|
10
|
+
if (process.env.NODE_ENV !== 'production') throw new Error(message);
|
|
11
|
+
warnOnce(`experiment:${slug}`, message);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
arm: entry?.arm ?? 'control',
|
|
15
|
+
params: entry?.params ?? {},
|
|
16
|
+
expose (renderedArm) {
|
|
17
|
+
if (!value || !entry?.exposable || entry.policy !== 'manual') return;
|
|
18
|
+
sendExposure(value.apiRoute, {
|
|
19
|
+
type: 'exposure',
|
|
20
|
+
experiment: slug,
|
|
21
|
+
phase: entry.phase,
|
|
22
|
+
arm: renderedArm,
|
|
23
|
+
policy: entry.policy
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DocId } from '../types/contracts.js';
|
|
2
|
+
export declare function useFlagPoint<P extends Record<string, unknown> = Record<string, unknown>>(id: string): {
|
|
3
|
+
arm: string;
|
|
4
|
+
params: P;
|
|
5
|
+
source: 'experiment' | 'default';
|
|
6
|
+
experiment?: string;
|
|
7
|
+
experimentId?: DocId;
|
|
8
|
+
expose(renderedArm: string): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { sendExposure, warnOnce } from './beacon.js';
|
|
4
|
+
import { ExperimentsContext } from './context.js';
|
|
5
|
+
export function useFlagPoint(id) {
|
|
6
|
+
const value = useContext(ExperimentsContext);
|
|
7
|
+
const entry = value && Object.hasOwn(value.flagPoints, id) ? value.flagPoints[id] : undefined;
|
|
8
|
+
if (!entry) {
|
|
9
|
+
const message = `Flag point "${id}" must be allow-listed in toClientAssignments and rendered inside ExperimentsProvider.`;
|
|
10
|
+
if (process.env.NODE_ENV !== 'production') throw new Error(message);
|
|
11
|
+
warnOnce(`flagPoint:${id}`, message);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
arm: entry?.arm ?? 'control',
|
|
15
|
+
params: entry?.params ?? {},
|
|
16
|
+
source: entry?.source ?? 'default',
|
|
17
|
+
...entry?.source === 'experiment' ? {
|
|
18
|
+
experiment: entry.experiment,
|
|
19
|
+
experimentId: entry.experimentId
|
|
20
|
+
} : {},
|
|
21
|
+
expose (renderedArm) {
|
|
22
|
+
if (!value || !entry?.exposable || entry.source !== 'experiment' || entry.policy !== 'manual' || entry.experiment === undefined || entry.phase === undefined) return;
|
|
23
|
+
sendExposure(value.apiRoute, {
|
|
24
|
+
type: 'exposure',
|
|
25
|
+
experiment: entry.experiment,
|
|
26
|
+
phase: entry.phase,
|
|
27
|
+
arm: renderedArm,
|
|
28
|
+
policy: entry.policy
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Access } from 'payload';
|
|
2
|
+
import type { ExperimentsAccess, ResolvedOptions } from '../types/contracts.js';
|
|
3
|
+
/** Host access callbacks must return the boolean true, never a truthy filter. */
|
|
4
|
+
export declare const isTrue: (fn: ExperimentsAccess) => Access;
|
|
5
|
+
export declare const deny: Access;
|
|
6
|
+
export declare function managedAccess(options: ResolvedOptions): {
|
|
7
|
+
create: Access;
|
|
8
|
+
read: Access;
|
|
9
|
+
update: Access;
|
|
10
|
+
delete: Access;
|
|
11
|
+
readVersions: Access;
|
|
12
|
+
unlock: Access;
|
|
13
|
+
};
|
|
14
|
+
export declare function readOnlyAccess(read: ExperimentsAccess): {
|
|
15
|
+
create: Access;
|
|
16
|
+
read: Access;
|
|
17
|
+
update: Access;
|
|
18
|
+
delete: Access;
|
|
19
|
+
readVersions: Access;
|
|
20
|
+
unlock: Access;
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Host access callbacks must return the boolean true, never a truthy filter. */ export const isTrue = (fn)=>async ({ req })=>await fn({
|
|
2
|
+
req
|
|
3
|
+
}) === true;
|
|
4
|
+
export const deny = ()=>false;
|
|
5
|
+
export function managedAccess(options) {
|
|
6
|
+
const manage = isTrue(options.access.manage);
|
|
7
|
+
return {
|
|
8
|
+
create: manage,
|
|
9
|
+
read: manage,
|
|
10
|
+
update: manage,
|
|
11
|
+
delete: manage,
|
|
12
|
+
readVersions: manage,
|
|
13
|
+
unlock: manage
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function readOnlyAccess(read) {
|
|
17
|
+
return {
|
|
18
|
+
create: deny,
|
|
19
|
+
read: isTrue(read),
|
|
20
|
+
update: deny,
|
|
21
|
+
delete: deny,
|
|
22
|
+
readVersions: deny,
|
|
23
|
+
unlock: deny
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readOnlyAccess } from './access.js';
|
|
2
|
+
export function eventsCollection(options) {
|
|
3
|
+
return {
|
|
4
|
+
slug: 'xp-events',
|
|
5
|
+
graphQL: false,
|
|
6
|
+
disableDuplicate: true,
|
|
7
|
+
admin: {
|
|
8
|
+
hidden: true
|
|
9
|
+
},
|
|
10
|
+
access: readOnlyAccess(options.access.readIdentities ?? options.access.admin ?? options.access.manage),
|
|
11
|
+
indexes: [
|
|
12
|
+
{
|
|
13
|
+
fields: [
|
|
14
|
+
'experiment',
|
|
15
|
+
'phase',
|
|
16
|
+
'type',
|
|
17
|
+
'ts'
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
fields: [
|
|
22
|
+
'unitId',
|
|
23
|
+
'experiment'
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
fields: [
|
|
28
|
+
'type',
|
|
29
|
+
'eventName',
|
|
30
|
+
'ts'
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
fields: [
|
|
35
|
+
'experiment',
|
|
36
|
+
'type',
|
|
37
|
+
'ts'
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
fields: [
|
|
42
|
+
{
|
|
43
|
+
name: 'type',
|
|
44
|
+
type: 'select',
|
|
45
|
+
required: true,
|
|
46
|
+
options: [
|
|
47
|
+
'exposure',
|
|
48
|
+
'conversion',
|
|
49
|
+
'arm-mismatch'
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'experiment',
|
|
54
|
+
type: 'relationship',
|
|
55
|
+
relationTo: 'xp-experiments'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'phase',
|
|
59
|
+
type: 'number'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'arm',
|
|
63
|
+
type: 'text'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'unitId',
|
|
67
|
+
type: 'text',
|
|
68
|
+
required: true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'unitType',
|
|
72
|
+
type: 'select',
|
|
73
|
+
required: true,
|
|
74
|
+
options: [
|
|
75
|
+
'visitor',
|
|
76
|
+
'user'
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'userId',
|
|
81
|
+
type: 'text'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'eventName',
|
|
85
|
+
type: 'text'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'value',
|
|
89
|
+
type: 'number'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'ts',
|
|
93
|
+
type: 'date',
|
|
94
|
+
required: true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'dimensions',
|
|
98
|
+
type: 'json',
|
|
99
|
+
admin: {
|
|
100
|
+
hidden: true,
|
|
101
|
+
readOnly: true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'attributes',
|
|
106
|
+
type: 'json'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'dedupeKey',
|
|
110
|
+
type: 'text',
|
|
111
|
+
unique: true,
|
|
112
|
+
index: true,
|
|
113
|
+
required: true
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Arm, ExperimentKind } from '../../types/contracts.js';
|
|
2
|
+
import type { ArmIssueCode } from '../../types/endpoints.js';
|
|
3
|
+
export interface ArmIssue {
|
|
4
|
+
code: ArmIssueCode;
|
|
5
|
+
armIndex?: number;
|
|
6
|
+
armKey?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
/** The groups a new experiment starts with: Original 50 / Variant A 50, no holdout (spec §5.1, O4). Block slots equal the key. */
|
|
10
|
+
export declare function defaultArms(kind: ExperimentKind | undefined): Arm[];
|
|
11
|
+
export declare function checkArms(arms: Arm[], kind: ExperimentKind): ArmIssue[];
|
|
12
|
+
export declare function addHoldout(arms: Arm[]): Arm[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** The groups a new experiment starts with: Original 50 / Variant A 50, no holdout (spec §5.1, O4). Block slots equal the key. */ export function defaultArms(kind) {
|
|
2
|
+
const slot = (key)=>kind === 'block' ? {
|
|
3
|
+
slot: key
|
|
4
|
+
} : {};
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
key: 'control',
|
|
8
|
+
kind: 'control',
|
|
9
|
+
label: 'Original',
|
|
10
|
+
weight: 50,
|
|
11
|
+
...slot('control')
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
key: 'variantA',
|
|
15
|
+
kind: 'variant',
|
|
16
|
+
label: 'Variant A',
|
|
17
|
+
weight: 50,
|
|
18
|
+
...slot('variantA')
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
export function checkArms(arms, kind) {
|
|
23
|
+
const issues = [];
|
|
24
|
+
const issue = (code, message, armIndex)=>{
|
|
25
|
+
issues.push({
|
|
26
|
+
code,
|
|
27
|
+
message,
|
|
28
|
+
...armIndex === undefined ? {} : {
|
|
29
|
+
armIndex,
|
|
30
|
+
armKey: arms[armIndex].key
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
if (arms.some((arm)=>!Number.isInteger(arm.weight) || arm.weight < 0) || arms.reduce((total, arm)=>total + arm.weight, 0) !== 100) {
|
|
35
|
+
issue('weights-total', 'Group shares must be whole numbers that add up to exactly 100.');
|
|
36
|
+
}
|
|
37
|
+
if (arms.filter((arm)=>arm.kind === 'control').length !== 1) issue('control-count', 'Exactly one Original is required.');
|
|
38
|
+
if (arms.some((arm, index)=>arm.kind === 'control' && index !== 0)) issue('control-first', 'The Original must be first.');
|
|
39
|
+
if (arms.filter((arm)=>arm.kind === 'holdout').length > 1) issue('holdout-count', 'Only one Holdout is allowed.');
|
|
40
|
+
if (arms.some((arm, index)=>arm.kind === 'holdout' && index !== arms.length - 1)) issue('holdout-last', 'The Holdout must be the last group.');
|
|
41
|
+
if (arms.filter((arm)=>arm.kind === 'variant').length > 3) issue('variant-count', 'At most three variants are allowed.');
|
|
42
|
+
const keys = new Set(), slots = new Set();
|
|
43
|
+
const kinds = {
|
|
44
|
+
control: 'control',
|
|
45
|
+
variantA: 'variant',
|
|
46
|
+
variantB: 'variant',
|
|
47
|
+
variantC: 'variant',
|
|
48
|
+
holdout: 'holdout'
|
|
49
|
+
};
|
|
50
|
+
arms.forEach((arm, index)=>{
|
|
51
|
+
if (!Object.hasOwn(kinds, arm.key) || kinds[arm.key] !== arm.kind) issue('invalid-key', 'Each group must be the Original, Variant A, B or C, or the Holdout.', index);
|
|
52
|
+
if (keys.has(arm.key)) issue('duplicate-key', 'Each group can appear only once.', index);
|
|
53
|
+
keys.add(arm.key);
|
|
54
|
+
if (kind === 'block') {
|
|
55
|
+
if (arm.kind === 'holdout') {
|
|
56
|
+
if (arm.slot != null) issue('invalid-key', 'The Holdout has no content of its own; it sees the Original.', index);
|
|
57
|
+
} else {
|
|
58
|
+
if (!arm.slot || arm.slot !== arm.key) issue('slot-required', 'Each Original and Variant needs its own content area.', index);
|
|
59
|
+
if (arm.slot && slots.has(arm.slot)) issue('duplicate-slot', 'Two groups cannot share the same content area.', index);
|
|
60
|
+
if (arm.slot) slots.add(arm.slot);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return issues;
|
|
65
|
+
}
|
|
66
|
+
export function addHoldout(arms) {
|
|
67
|
+
if (arms.some((arm)=>arm.kind === 'holdout' || arm.key === 'holdout')) return arms;
|
|
68
|
+
const total = arms.reduce((sum, arm)=>sum + arm.weight, 0);
|
|
69
|
+
const denominator = total > 0 ? total : arms.length;
|
|
70
|
+
const numerators = arms.map((arm)=>(total > 0 ? arm.weight : 1) * 90);
|
|
71
|
+
const weights = numerators.map((numerator)=>Math.floor(numerator / denominator));
|
|
72
|
+
const remainders = numerators.map((numerator)=>numerator % denominator);
|
|
73
|
+
const order = arms.map((_, index)=>index).sort((a, b)=>remainders[b] - remainders[a] || Number(arms[b].kind === 'control') - Number(arms[a].kind === 'control') || a - b);
|
|
74
|
+
const remainder = 90 - weights.reduce((sum, weight)=>sum + weight, 0);
|
|
75
|
+
for(let i = 0; i < remainder && order.length; i++)weights[order[i % order.length]]++;
|
|
76
|
+
const holdout = {
|
|
77
|
+
key: 'holdout',
|
|
78
|
+
kind: 'holdout',
|
|
79
|
+
label: 'Holdout',
|
|
80
|
+
weight: 10
|
|
81
|
+
};
|
|
82
|
+
return [
|
|
83
|
+
...arms.map((arm, index)=>({
|
|
84
|
+
...arm,
|
|
85
|
+
weight: weights[index]
|
|
86
|
+
})),
|
|
87
|
+
holdout
|
|
88
|
+
];
|
|
89
|
+
}
|