@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.
- package/LICENSE +21 -0
- package/README.md +582 -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 +99 -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 +133 -0
- package/dist/types/contracts.js +2 -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 +137 -3
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createUsePuck, registerOverlayPortal, useGetPuck } from '@puckeditor/core';
|
|
4
|
+
import { useEffect, useId, useState } from 'react';
|
|
5
|
+
import { pickerCopy } from '../copy.js';
|
|
6
|
+
import { experimentSlotDefaultProps, experimentSlotFields, experimentSlotLabel } from '../slotConfig.js';
|
|
7
|
+
import { bannerFor, tabsFor } from './editor.model.js';
|
|
8
|
+
import { cloneSlot } from './cloneSlot.js';
|
|
9
|
+
import { CreateExperimentDrawer } from './CreateExperimentDrawer.js';
|
|
10
|
+
import { editorCopy } from './copy.js';
|
|
11
|
+
const usePuckSelector = createUsePuck();
|
|
12
|
+
// Public Puck registration enables native controls inside the editor canvas.
|
|
13
|
+
const registerControls = (element)=>registerOverlayPortal(element, {
|
|
14
|
+
disableDrag: true
|
|
15
|
+
});
|
|
16
|
+
function isExperiment(value) {
|
|
17
|
+
if (!value || typeof value !== 'object') return false;
|
|
18
|
+
const doc = value;
|
|
19
|
+
return typeof doc.name === 'string' && typeof doc.status === 'string' && Object.hasOwn(pickerCopy.statuses, doc.status) && Array.isArray(doc.arms) && doc.arms.every((arm)=>arm && typeof arm === 'object' && (Object.hasOwn(editorCopy.labels, arm.key) || arm.key === 'holdout') && [
|
|
20
|
+
'control',
|
|
21
|
+
'variant',
|
|
22
|
+
'holdout'
|
|
23
|
+
].includes(arm.kind) && typeof arm.label === 'string');
|
|
24
|
+
}
|
|
25
|
+
function EditorRender(props) {
|
|
26
|
+
const getPuck = useGetPuck();
|
|
27
|
+
const tabId = useId();
|
|
28
|
+
const experimentId = props.experiment?.id;
|
|
29
|
+
const [loaded, setLoaded] = useState();
|
|
30
|
+
const [active, setActive] = useState();
|
|
31
|
+
const [creating, setCreating] = useState(false);
|
|
32
|
+
// A non-empty variant is only overwritten after an explicit second confirmation.
|
|
33
|
+
const [confirming, setConfirming] = useState(null);
|
|
34
|
+
const selected = usePuckSelector((state)=>state.selectedItem?.props.id === props.id);
|
|
35
|
+
const originalEmpty = usePuckSelector((state)=>{
|
|
36
|
+
const content = state.selectedItem?.props.control;
|
|
37
|
+
return state.selectedItem?.props.id !== props.id || !Array.isArray(content) || content.length === 0;
|
|
38
|
+
});
|
|
39
|
+
useEffect(()=>{
|
|
40
|
+
if (experimentId === undefined) return;
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
let current = true;
|
|
43
|
+
async function load() {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`/api/xp-experiments/${encodeURIComponent(experimentId)}?depth=0`, {
|
|
46
|
+
credentials: 'include',
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) throw new Error('load');
|
|
50
|
+
const doc = await response.json();
|
|
51
|
+
if (!isExperiment(doc)) throw new Error('document');
|
|
52
|
+
if (current) setLoaded({
|
|
53
|
+
id: experimentId,
|
|
54
|
+
doc
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
if (current) setLoaded({
|
|
58
|
+
id: experimentId,
|
|
59
|
+
error: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
void load();
|
|
64
|
+
return ()=>{
|
|
65
|
+
current = false;
|
|
66
|
+
controller.abort();
|
|
67
|
+
};
|
|
68
|
+
}, [
|
|
69
|
+
experimentId
|
|
70
|
+
]);
|
|
71
|
+
const result = experimentId === undefined || loaded?.id !== experimentId ? undefined : loaded;
|
|
72
|
+
const experiment = result?.doc;
|
|
73
|
+
const tabs = tabsFor(experiment);
|
|
74
|
+
const activeSlot = active && active.id === experimentId ? active.slot : undefined;
|
|
75
|
+
const slot = tabs.find((tab)=>tab.slot === activeSlot)?.slot ?? tabs[0]?.slot ?? 'control';
|
|
76
|
+
const Slot = props[slot];
|
|
77
|
+
const winnerKey = experiment?.decision?.winnerArm;
|
|
78
|
+
const winner = tabs.find((tab)=>tab.slot === winnerKey)?.label;
|
|
79
|
+
function requestCopy(variant) {
|
|
80
|
+
const { selectedItem: item } = getPuck();
|
|
81
|
+
const target = item?.props.id === props.id ? item.props[variant] : undefined;
|
|
82
|
+
if (Array.isArray(target) && target.length) setConfirming(variant);
|
|
83
|
+
else copyTo(variant);
|
|
84
|
+
}
|
|
85
|
+
function copyTo(variant) {
|
|
86
|
+
setConfirming(null);
|
|
87
|
+
const { selectedItem: item, dispatch, getSelectorForId } = getPuck();
|
|
88
|
+
if (!item || item.props.id !== props.id || !Array.isArray(item.props.control) || !item.props.control.length) return;
|
|
89
|
+
const selector = getSelectorForId(props.id);
|
|
90
|
+
if (!selector) return;
|
|
91
|
+
dispatch({
|
|
92
|
+
type: 'replace',
|
|
93
|
+
destinationIndex: selector.index,
|
|
94
|
+
destinationZone: selector.zone,
|
|
95
|
+
data: {
|
|
96
|
+
...item,
|
|
97
|
+
props: {
|
|
98
|
+
...item.props,
|
|
99
|
+
[variant]: cloneSlot(item.props.control)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function bind(experiment) {
|
|
105
|
+
const { getItemById, dispatch, getSelectorForId } = getPuck();
|
|
106
|
+
const item = getItemById(props.id), selector = getSelectorForId(props.id);
|
|
107
|
+
if (!item || !selector) return;
|
|
108
|
+
dispatch({
|
|
109
|
+
type: 'replace',
|
|
110
|
+
destinationIndex: selector.index,
|
|
111
|
+
destinationZone: selector.zone,
|
|
112
|
+
data: {
|
|
113
|
+
...item,
|
|
114
|
+
props: {
|
|
115
|
+
...item.props,
|
|
116
|
+
experiment
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
122
|
+
style: {
|
|
123
|
+
border: '1px solid #aaa',
|
|
124
|
+
padding: 12
|
|
125
|
+
},
|
|
126
|
+
children: [
|
|
127
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
128
|
+
ref: registerControls,
|
|
129
|
+
children: [
|
|
130
|
+
/*#__PURE__*/ _jsx("p", {
|
|
131
|
+
role: "status",
|
|
132
|
+
children: experimentId === undefined ? bannerFor() : result?.error ? editorCopy.loadError : experiment ? bannerFor(experiment.status, winner) : editorCopy.loading
|
|
133
|
+
}),
|
|
134
|
+
/*#__PURE__*/ _jsx("button", {
|
|
135
|
+
type: "button",
|
|
136
|
+
disabled: !selected,
|
|
137
|
+
onClick: ()=>setCreating(true),
|
|
138
|
+
children: editorCopy.createLabel
|
|
139
|
+
}),
|
|
140
|
+
creating ? /*#__PURE__*/ _jsx(CreateExperimentDrawer, {
|
|
141
|
+
onCreate: bind,
|
|
142
|
+
onClose: ()=>setCreating(false)
|
|
143
|
+
}) : null,
|
|
144
|
+
/*#__PURE__*/ _jsx("div", {
|
|
145
|
+
style: {
|
|
146
|
+
display: 'flex',
|
|
147
|
+
flexWrap: 'wrap',
|
|
148
|
+
gap: 8,
|
|
149
|
+
marginBlock: 8
|
|
150
|
+
},
|
|
151
|
+
children: tabs.filter((tab)=>tab.slot !== 'control').map((tab)=>/*#__PURE__*/ _jsx("button", {
|
|
152
|
+
type: "button",
|
|
153
|
+
disabled: originalEmpty,
|
|
154
|
+
onClick: ()=>requestCopy(tab.slot),
|
|
155
|
+
children: editorCopy.copy(tab.label)
|
|
156
|
+
}, tab.slot))
|
|
157
|
+
}),
|
|
158
|
+
confirming ? /*#__PURE__*/ _jsxs("p", {
|
|
159
|
+
role: "alert",
|
|
160
|
+
children: [
|
|
161
|
+
editorCopy.replaceConfirm(tabs.find((tab)=>tab.slot === confirming)?.label ?? confirming),
|
|
162
|
+
' ',
|
|
163
|
+
/*#__PURE__*/ _jsx("button", {
|
|
164
|
+
type: "button",
|
|
165
|
+
onClick: ()=>copyTo(confirming),
|
|
166
|
+
children: editorCopy.replace
|
|
167
|
+
}),
|
|
168
|
+
' ',
|
|
169
|
+
/*#__PURE__*/ _jsx("button", {
|
|
170
|
+
type: "button",
|
|
171
|
+
onClick: ()=>setConfirming(null),
|
|
172
|
+
children: editorCopy.cancel
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
}) : null,
|
|
176
|
+
/*#__PURE__*/ _jsx("div", {
|
|
177
|
+
role: "tablist",
|
|
178
|
+
"aria-label": editorCopy.tabs,
|
|
179
|
+
style: {
|
|
180
|
+
display: 'flex',
|
|
181
|
+
gap: 8,
|
|
182
|
+
marginBottom: 8
|
|
183
|
+
},
|
|
184
|
+
children: tabs.map((tab, index)=>/*#__PURE__*/ _jsx("button", {
|
|
185
|
+
type: "button",
|
|
186
|
+
role: "tab",
|
|
187
|
+
id: `${tabId}-${tab.slot}`,
|
|
188
|
+
"aria-selected": tab.slot === slot,
|
|
189
|
+
tabIndex: tab.slot === slot ? 0 : -1,
|
|
190
|
+
"aria-controls": `${tabId}-panel`,
|
|
191
|
+
onClick: ()=>setActive({
|
|
192
|
+
id: experimentId,
|
|
193
|
+
slot: tab.slot
|
|
194
|
+
}),
|
|
195
|
+
onKeyDown: (event)=>{
|
|
196
|
+
const next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : undefined;
|
|
197
|
+
if (next === undefined) return;
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
setActive({
|
|
200
|
+
id: experimentId,
|
|
201
|
+
slot: tabs[next].slot
|
|
202
|
+
});
|
|
203
|
+
const buttons = event.currentTarget.parentElement?.querySelectorAll('[role="tab"]');
|
|
204
|
+
buttons?.[next]?.focus();
|
|
205
|
+
},
|
|
206
|
+
children: tab.label
|
|
207
|
+
}, tab.slot))
|
|
208
|
+
})
|
|
209
|
+
]
|
|
210
|
+
}),
|
|
211
|
+
/*#__PURE__*/ _jsx("div", {
|
|
212
|
+
role: "tabpanel",
|
|
213
|
+
id: `${tabId}-panel`,
|
|
214
|
+
"aria-labelledby": `${tabId}-${slot}`,
|
|
215
|
+
children: /*#__PURE__*/ _jsx(Slot, {})
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
export const ExperimentSlotEditor = {
|
|
221
|
+
label: experimentSlotLabel,
|
|
222
|
+
fields: experimentSlotFields,
|
|
223
|
+
defaultProps: experimentSlotDefaultProps,
|
|
224
|
+
render: EditorRender
|
|
225
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
let sequence = 0;
|
|
2
|
+
function freshId(type, used) {
|
|
3
|
+
let random;
|
|
4
|
+
try {
|
|
5
|
+
const bytes = new Uint32Array(4);
|
|
6
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
7
|
+
random = Array.from(bytes, (byte)=>byte.toString(36)).join('');
|
|
8
|
+
} catch {
|
|
9
|
+
random = Math.random().toString(36).slice(2);
|
|
10
|
+
}
|
|
11
|
+
let id;
|
|
12
|
+
do {
|
|
13
|
+
id = `${type}-${random}${(++sequence).toString(36)}`;
|
|
14
|
+
}while (used.has(id))
|
|
15
|
+
used.add(id);
|
|
16
|
+
return id;
|
|
17
|
+
}
|
|
18
|
+
/** Clone JSON Puck slot data, refreshing component ids even inside nested slots. */ export function cloneSlot(slot) {
|
|
19
|
+
const used = new Set();
|
|
20
|
+
function collect(value) {
|
|
21
|
+
if (!value || typeof value !== 'object') return;
|
|
22
|
+
if ('id' in value && typeof value.id === 'string') used.add(value.id);
|
|
23
|
+
Object.values(value).forEach(collect);
|
|
24
|
+
}
|
|
25
|
+
collect(slot);
|
|
26
|
+
function clone(value) {
|
|
27
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
28
|
+
if (!value || typeof value !== 'object') return value;
|
|
29
|
+
const source = value;
|
|
30
|
+
const result = Object.fromEntries(Object.entries(source).map(([key, entry])=>[
|
|
31
|
+
key,
|
|
32
|
+
clone(entry)
|
|
33
|
+
]));
|
|
34
|
+
if (typeof source.type === 'string' && result.props && typeof result.props === 'object') {
|
|
35
|
+
const props = result.props;
|
|
36
|
+
props.id = freshId(source.type, used);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
return clone(slot);
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const editorCopy: {
|
|
2
|
+
readonly labels: {
|
|
3
|
+
readonly control: "Original";
|
|
4
|
+
readonly variantA: "Variant A";
|
|
5
|
+
readonly variantB: "Variant B";
|
|
6
|
+
readonly variantC: "Variant C";
|
|
7
|
+
};
|
|
8
|
+
readonly unbound: "Choose or create an experiment to show different versions.";
|
|
9
|
+
readonly draft: "This block shows the Original to everyone until the experiment starts.";
|
|
10
|
+
readonly running: "Changing this content mid-test mixes two versions in one result";
|
|
11
|
+
readonly decided: (winner: string) => string;
|
|
12
|
+
readonly archived: "This experiment is archived; the winning content is shown to everyone.";
|
|
13
|
+
readonly copy: (label: string) => string;
|
|
14
|
+
readonly replaceConfirm: (label: string) => string;
|
|
15
|
+
readonly replace: "Replace";
|
|
16
|
+
readonly createLabel: "Create experiment";
|
|
17
|
+
readonly creating: "Creating…";
|
|
18
|
+
readonly cancel: "Cancel";
|
|
19
|
+
readonly name: "Name";
|
|
20
|
+
readonly slug: "Slug";
|
|
21
|
+
readonly createError: "Couldn't create the experiment. Try again.";
|
|
22
|
+
readonly loading: "Loading experiment…";
|
|
23
|
+
readonly loadError: "Couldn't load this experiment. Try selecting it again.";
|
|
24
|
+
readonly tabs: "Experiment versions";
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const editorCopy = {
|
|
2
|
+
labels: {
|
|
3
|
+
control: 'Original',
|
|
4
|
+
variantA: 'Variant A',
|
|
5
|
+
variantB: 'Variant B',
|
|
6
|
+
variantC: 'Variant C'
|
|
7
|
+
},
|
|
8
|
+
unbound: 'Choose or create an experiment to show different versions.',
|
|
9
|
+
draft: 'This block shows the Original to everyone until the experiment starts.',
|
|
10
|
+
running: 'Changing this content mid-test mixes two versions in one result',
|
|
11
|
+
decided: (winner)=>`Decided: ${winner}. Use 'Make it the content' on the experiment to finish.`,
|
|
12
|
+
archived: 'This experiment is archived; the winning content is shown to everyone.',
|
|
13
|
+
copy: (label)=>`Copy Original → ${label}`,
|
|
14
|
+
replaceConfirm: (label)=>`${label} already has content. Replace it with a copy of the Original?`,
|
|
15
|
+
replace: 'Replace',
|
|
16
|
+
createLabel: 'Create experiment',
|
|
17
|
+
creating: 'Creating…',
|
|
18
|
+
cancel: 'Cancel',
|
|
19
|
+
name: 'Name',
|
|
20
|
+
slug: 'Slug',
|
|
21
|
+
createError: "Couldn't create the experiment. Try again.",
|
|
22
|
+
loading: 'Loading experiment…',
|
|
23
|
+
loadError: "Couldn't load this experiment. Try selecting it again.",
|
|
24
|
+
tabs: 'Experiment versions'
|
|
25
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Arm, Status } from '../../types/contracts.js';
|
|
2
|
+
import type { ArmSlot } from '../../types/puck.js';
|
|
3
|
+
export type EditorExperiment = {
|
|
4
|
+
name: string;
|
|
5
|
+
status: Status;
|
|
6
|
+
arms: Array<Pick<Arm, 'key' | 'kind' | 'label'>>;
|
|
7
|
+
decision?: {
|
|
8
|
+
winnerArm?: string;
|
|
9
|
+
} | null;
|
|
10
|
+
};
|
|
11
|
+
export declare function tabsFor(experiment?: Pick<EditorExperiment, 'arms'> | null): Array<{
|
|
12
|
+
slot: ArmSlot;
|
|
13
|
+
label: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function bannerFor(status?: Status | null, winner?: string): string;
|
|
16
|
+
export declare function slugify(name: string): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { editorCopy } from './copy.js';
|
|
2
|
+
export function tabsFor(experiment) {
|
|
3
|
+
if (!experiment) return [
|
|
4
|
+
{
|
|
5
|
+
slot: 'control',
|
|
6
|
+
label: editorCopy.labels.control
|
|
7
|
+
}
|
|
8
|
+
];
|
|
9
|
+
return experiment.arms.flatMap((arm)=>arm.kind === 'holdout' || arm.key === 'holdout' ? [] : [
|
|
10
|
+
{
|
|
11
|
+
slot: arm.key,
|
|
12
|
+
label: arm.label?.trim() ? arm.label : editorCopy.labels[arm.key]
|
|
13
|
+
}
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
export function bannerFor(status, winner = editorCopy.labels.control) {
|
|
17
|
+
switch(status){
|
|
18
|
+
case 'draft':
|
|
19
|
+
case 'stopped':
|
|
20
|
+
return editorCopy.draft;
|
|
21
|
+
case 'running':
|
|
22
|
+
return editorCopy.running;
|
|
23
|
+
case 'decided':
|
|
24
|
+
return editorCopy.decided(winner);
|
|
25
|
+
case 'archived':
|
|
26
|
+
return editorCopy.archived;
|
|
27
|
+
default:
|
|
28
|
+
return editorCopy.unbound;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function slugify(name) {
|
|
32
|
+
return name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64).replace(/-+$/g, '');
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { ExperimentPicker } from './ExperimentPicker.js';
|
|
3
|
+
import { pickerCopy } from './copy.js';
|
|
4
|
+
export function createExperimentSlotField(opts) {
|
|
5
|
+
return {
|
|
6
|
+
type: 'custom',
|
|
7
|
+
label: opts?.label ?? pickerCopy.label,
|
|
8
|
+
render: ({ value, onChange })=>createElement(ExperimentPicker, {
|
|
9
|
+
value,
|
|
10
|
+
onChange
|
|
11
|
+
})
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentConfig } from '@puckeditor/core';
|
|
2
|
+
import type { ExperimentSlotProps } from '../types/puck.js';
|
|
3
|
+
export declare const experimentSlotLabel: "Experiment";
|
|
4
|
+
export declare const experimentSlotFields: NonNullable<ComponentConfig<ExperimentSlotProps>['fields']>;
|
|
5
|
+
export declare const experimentSlotDefaultProps: NonNullable<ComponentConfig<ExperimentSlotProps>['defaultProps']>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createExperimentSlotField } from './experimentSlotField.js';
|
|
2
|
+
import { pickerCopy } from './copy.js';
|
|
3
|
+
export const experimentSlotLabel = pickerCopy.label;
|
|
4
|
+
export const experimentSlotFields = {
|
|
5
|
+
experiment: createExperimentSlotField(),
|
|
6
|
+
control: {
|
|
7
|
+
type: 'slot'
|
|
8
|
+
},
|
|
9
|
+
variantA: {
|
|
10
|
+
type: 'slot'
|
|
11
|
+
},
|
|
12
|
+
variantB: {
|
|
13
|
+
type: 'slot'
|
|
14
|
+
},
|
|
15
|
+
variantC: {
|
|
16
|
+
type: 'slot'
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const experimentSlotDefaultProps = {
|
|
20
|
+
experiment: null,
|
|
21
|
+
control: [],
|
|
22
|
+
variantA: [],
|
|
23
|
+
variantB: [],
|
|
24
|
+
variantC: []
|
|
25
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type Timestamp = Date | string | number;
|
|
2
|
+
export interface ExposedUnit {
|
|
3
|
+
arm: string;
|
|
4
|
+
firstTs: Timestamp;
|
|
5
|
+
}
|
|
6
|
+
export interface MetricEvent {
|
|
7
|
+
unitId: string;
|
|
8
|
+
value: number;
|
|
9
|
+
ts: Timestamp;
|
|
10
|
+
}
|
|
11
|
+
/** Accept Date, timestamp text, or epoch milliseconds; reject invalid dates. */
|
|
12
|
+
export declare function timestampMs(ts: Timestamp): number;
|
|
13
|
+
/** Attribution windows must have a finite, positive length in hours. */
|
|
14
|
+
export declare function windowMs(windowHours: number): number;
|
|
15
|
+
/** Spec §7.6/§8.2: first exposure in a phase, excluding units seen in multiple arms. */
|
|
16
|
+
export declare function firstExposures(rows: readonly {
|
|
17
|
+
unitId: string;
|
|
18
|
+
arm: string;
|
|
19
|
+
ts: Timestamp;
|
|
20
|
+
}[], { endedAt }?: {
|
|
21
|
+
endedAt?: Timestamp;
|
|
22
|
+
}): {
|
|
23
|
+
units: Map<string, {
|
|
24
|
+
arm: string;
|
|
25
|
+
firstTs: Date;
|
|
26
|
+
}>;
|
|
27
|
+
excludedMultiArm: number;
|
|
28
|
+
};
|
|
29
|
+
/** Spec §7.6/§8.2: zero-filled outcomes over each exposed unit's half-open window. */
|
|
30
|
+
export declare function perUnitValues(units: ReadonlyMap<string, ExposedUnit>, events: readonly MetricEvent[], { kind, windowHours, endedAt }: {
|
|
31
|
+
kind: 'binomial' | 'count' | 'revenue';
|
|
32
|
+
windowHours: number;
|
|
33
|
+
endedAt?: Timestamp;
|
|
34
|
+
}): Map<string, number>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Accept Date, timestamp text, or epoch milliseconds; reject invalid dates. */ export function timestampMs(ts) {
|
|
2
|
+
if (!(ts instanceof Date) && typeof ts !== 'string' && typeof ts !== 'number') {
|
|
3
|
+
throw new RangeError('Invalid timestamp');
|
|
4
|
+
}
|
|
5
|
+
const ms = ts instanceof Date ? ts.getTime() : new Date(ts).getTime();
|
|
6
|
+
if (!Number.isFinite(ms)) throw new RangeError('Invalid timestamp');
|
|
7
|
+
return ms;
|
|
8
|
+
}
|
|
9
|
+
/** Attribution windows must have a finite, positive length in hours. */ export function windowMs(windowHours) {
|
|
10
|
+
if (!Number.isFinite(windowHours) || windowHours <= 0) {
|
|
11
|
+
throw new RangeError('windowHours must be a finite number greater than 0');
|
|
12
|
+
}
|
|
13
|
+
return windowHours * 3_600_000;
|
|
14
|
+
}
|
|
15
|
+
/** Spec §7.6/§8.2: first exposure in a phase, excluding units seen in multiple arms. */ export function firstExposures(rows, { endedAt } = {}) {
|
|
16
|
+
const first = new Map();
|
|
17
|
+
const excluded = new Set();
|
|
18
|
+
const end = endedAt === undefined ? Infinity : timestampMs(endedAt);
|
|
19
|
+
for (const row of rows){
|
|
20
|
+
const ts = timestampMs(row.ts);
|
|
21
|
+
if (ts >= end || excluded.has(row.unitId)) continue;
|
|
22
|
+
const unit = first.get(row.unitId);
|
|
23
|
+
if (!unit) first.set(row.unitId, {
|
|
24
|
+
arm: row.arm,
|
|
25
|
+
firstTs: ts
|
|
26
|
+
});
|
|
27
|
+
else if (unit.arm !== row.arm) {
|
|
28
|
+
first.delete(row.unitId);
|
|
29
|
+
excluded.add(row.unitId);
|
|
30
|
+
} else if (ts < unit.firstTs) {
|
|
31
|
+
first.set(row.unitId, {
|
|
32
|
+
arm: row.arm,
|
|
33
|
+
firstTs: ts
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const units = new Map();
|
|
38
|
+
for (const [unitId, unit] of first){
|
|
39
|
+
units.set(unitId, {
|
|
40
|
+
arm: unit.arm,
|
|
41
|
+
firstTs: new Date(unit.firstTs)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
units,
|
|
46
|
+
excludedMultiArm: excluded.size
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Spec §7.6/§8.2: zero-filled outcomes over each exposed unit's half-open window. */ export function perUnitValues(units, events, { kind, windowHours, endedAt }) {
|
|
50
|
+
const values = new Map();
|
|
51
|
+
const starts = new Map();
|
|
52
|
+
const duration = windowMs(windowHours);
|
|
53
|
+
const phaseEnd = endedAt === undefined ? Infinity : timestampMs(endedAt);
|
|
54
|
+
for (const [unitId, unit] of units){
|
|
55
|
+
starts.set(unitId, timestampMs(unit.firstTs));
|
|
56
|
+
values.set(unitId, 0);
|
|
57
|
+
}
|
|
58
|
+
for (const event of events){
|
|
59
|
+
const ts = timestampMs(event.ts);
|
|
60
|
+
const start = starts.get(event.unitId);
|
|
61
|
+
if (start === undefined) continue;
|
|
62
|
+
if (ts < start || ts >= Math.min(start + duration, phaseEnd)) continue;
|
|
63
|
+
values.set(event.unitId, kind === 'binomial' ? 1 : values.get(event.unitId) + (kind === 'count' ? 1 : event.value));
|
|
64
|
+
}
|
|
65
|
+
return values;
|
|
66
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { GaussianPrior } from '@delmaredigital/experiments-stats';
|
|
2
|
+
import type { Arm, DocId, MetricSource } from '../types/contracts.js';
|
|
3
|
+
import type { Snapshot } from '../types/snapshot.js';
|
|
4
|
+
import type { ExposedUnit, Timestamp } from './aggregate.js';
|
|
5
|
+
export interface FrozenMetric {
|
|
6
|
+
metricId: DocId;
|
|
7
|
+
name: string;
|
|
8
|
+
kind: MetricSource['kind'];
|
|
9
|
+
role: 'primary' | 'secondary' | 'guardrail';
|
|
10
|
+
direction: 'increase' | 'decrease';
|
|
11
|
+
attributionWindowHours: number;
|
|
12
|
+
minSampleSize: number;
|
|
13
|
+
guardrailThreshold?: number;
|
|
14
|
+
capping?: {
|
|
15
|
+
type: 'percentile' | 'absolute';
|
|
16
|
+
value: number;
|
|
17
|
+
};
|
|
18
|
+
resolvedCap?: number | null;
|
|
19
|
+
historyUnits?: number;
|
|
20
|
+
}
|
|
21
|
+
/** The caller decodes the phase JSON into this shape. No live metric or experiment
|
|
22
|
+
* settings are read by analysis. Rollout arms carry weights {100-h,h} and include
|
|
23
|
+
* only the shipped arm and holdout; units have already passed rolloutAnalysisUnits.
|
|
24
|
+
*/
|
|
25
|
+
export interface AnalysisPhase {
|
|
26
|
+
experiment: DocId;
|
|
27
|
+
phase: number;
|
|
28
|
+
phaseKind: 'test' | 'rollout';
|
|
29
|
+
startedAt: Timestamp;
|
|
30
|
+
endedAt?: Timestamp | null;
|
|
31
|
+
arms: readonly Pick<Arm, 'key' | 'label' | 'kind' | 'weight'>[];
|
|
32
|
+
metricDefinitions: readonly FrozenMetric[];
|
|
33
|
+
analysis: {
|
|
34
|
+
method: 'sequential' | 'bayesian';
|
|
35
|
+
alpha?: number;
|
|
36
|
+
sequentialTuningParameter: number;
|
|
37
|
+
cuped: 'auto' | 'off';
|
|
38
|
+
cupedLookbackDays?: number;
|
|
39
|
+
minExposuresPerArm: number;
|
|
40
|
+
plannedDurationDays: number;
|
|
41
|
+
attributionWindowHours?: number;
|
|
42
|
+
minSampleSizes?: Record<DocId, number>;
|
|
43
|
+
guardrailThresholds?: Record<DocId, number>;
|
|
44
|
+
prior?: GaussianPrior;
|
|
45
|
+
bayesian?: {
|
|
46
|
+
ctw: number;
|
|
47
|
+
risk: number;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
contentChanges?: Snapshot['contentChanges'];
|
|
51
|
+
}
|
|
52
|
+
export interface MetricUnitData {
|
|
53
|
+
metricId: DocId;
|
|
54
|
+
/** CORE-14a perUnitValues output, zero-filled over exposed units only. */
|
|
55
|
+
values: ReadonlyMap<string, number>;
|
|
56
|
+
/** Host pre-period outcomes; absent entries in a supplied map mean zero. */
|
|
57
|
+
covariates?: ReadonlyMap<string, number>;
|
|
58
|
+
unavailable?: string;
|
|
59
|
+
}
|
|
60
|
+
export type AnalysePhaseInput = {
|
|
61
|
+
phase: AnalysisPhase;
|
|
62
|
+
status: 'running' | 'stopped' | 'decided';
|
|
63
|
+
now: Timestamp;
|
|
64
|
+
/** CORE-14a firstExposures output; same-unit multi-arm units already excluded. */
|
|
65
|
+
units: ReadonlyMap<string, ExposedUnit>;
|
|
66
|
+
metricData: readonly MetricUnitData[];
|
|
67
|
+
excludedMultiArm?: number;
|
|
68
|
+
armMismatch?: number;
|
|
69
|
+
/** Diagnostic from identity credit, supplied by the caller with its denominator. */
|
|
70
|
+
identityConflictShare?: number;
|
|
71
|
+
} & ({
|
|
72
|
+
winnerArm?: never;
|
|
73
|
+
} | {
|
|
74
|
+
winnerArm: string;
|
|
75
|
+
});
|
|
76
|
+
/** Pure exposure-based analysis (§6, §7.6, §8.4). All inference, moments, capping,
|
|
77
|
+
* CUPED, power and SRM are delegated to experiments-stats; now is always injected.
|
|
78
|
+
*/
|
|
79
|
+
export declare function analysePhase(input: AnalysePhaseInput): Snapshot;
|