@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,307 @@
|
|
|
1
|
+
import { resultsCandidates, resultsExecutor, writeSnapshots } from '../db/snapshots.js';
|
|
2
|
+
import { sweepEvents } from '../db/sweep.js';
|
|
3
|
+
import { armMismatchCount, identityRows, metricEventRows, phaseExposureRows, rolloutExposureRows } from '../db/results.js';
|
|
4
|
+
import { raceDeadline, ResultsBudgetError, RESULTS_TIMEOUT_MARGIN_MS } from '../db/executor.js';
|
|
5
|
+
import { analysePhase } from '../results/analyse.js';
|
|
6
|
+
import { firstExposures, perUnitValues, timestampMs } from '../results/aggregate.js';
|
|
7
|
+
import { rolloutAnalysisUnits } from '../results/rolloutUnits.js';
|
|
8
|
+
import { creditUserEvents } from '../results/identityCredit.js';
|
|
9
|
+
async function analyseCandidate(exec, candidate, options, now, deadline, unavailableMetrics) {
|
|
10
|
+
const { phase } = candidate;
|
|
11
|
+
// Rollout assignment preserves the original test ranges, but its SRM compares
|
|
12
|
+
// only the shipped group against holdout with weights {100-h, h}.
|
|
13
|
+
const analysisPhase = phase.phaseKind === 'rollout' ? {
|
|
14
|
+
...phase,
|
|
15
|
+
arms: phase.arms.map((arm)=>({
|
|
16
|
+
...arm,
|
|
17
|
+
weight: arm.kind === 'holdout' ? candidate.testArms.at(-1).weight : 100 - candidate.testArms.at(-1).weight
|
|
18
|
+
}))
|
|
19
|
+
} : phase;
|
|
20
|
+
const to = new Date(Math.min(+now, phase.endedAt == null ? Infinity : timestampMs(phase.endedAt)));
|
|
21
|
+
const query = {
|
|
22
|
+
experimentId: phase.experiment,
|
|
23
|
+
phase: phase.phase,
|
|
24
|
+
endedAt: to
|
|
25
|
+
};
|
|
26
|
+
const exposed = firstExposures(await phaseExposureRows(exec, query), {
|
|
27
|
+
endedAt: to
|
|
28
|
+
});
|
|
29
|
+
let units = exposed.units;
|
|
30
|
+
if (phase.phaseKind === 'rollout') {
|
|
31
|
+
const eligible = rolloutAnalysisUnits({
|
|
32
|
+
exposuresAllPhases: await rolloutExposureRows(exec, {
|
|
33
|
+
...query,
|
|
34
|
+
respectPhaseEnds: true
|
|
35
|
+
}),
|
|
36
|
+
rolloutPhase: phase.phase,
|
|
37
|
+
seed: phase.seed,
|
|
38
|
+
testWeights: candidate.testArms.map((arm)=>arm.weight),
|
|
39
|
+
coverage: candidate.layerSlug ? 1 : phase.allocation / 100,
|
|
40
|
+
...candidate.layerSlug && candidate.layerSlices?.length ? {
|
|
41
|
+
layer: {
|
|
42
|
+
slug: candidate.layerSlug,
|
|
43
|
+
slices: candidate.layerSlices
|
|
44
|
+
}
|
|
45
|
+
} : {}
|
|
46
|
+
});
|
|
47
|
+
units = new Map([
|
|
48
|
+
...units
|
|
49
|
+
].filter(([id])=>eligible.has(id)));
|
|
50
|
+
}
|
|
51
|
+
const ids = [
|
|
52
|
+
...units.keys()
|
|
53
|
+
];
|
|
54
|
+
const identities = candidate.unitType === 'visitor' ? await identityRows(exec, {
|
|
55
|
+
anonymousIds: ids
|
|
56
|
+
}) : [];
|
|
57
|
+
const userIds = [
|
|
58
|
+
...new Set(identities.map((identity)=>identity.userId))
|
|
59
|
+
];
|
|
60
|
+
const linked = new Set(userIds);
|
|
61
|
+
const credit = (events, userKeyed)=>{
|
|
62
|
+
if (candidate.unitType === 'user') return events.map((event)=>({
|
|
63
|
+
...event,
|
|
64
|
+
unitId: event.userId ?? event.unitId
|
|
65
|
+
}));
|
|
66
|
+
const userEvents = events.filter((event)=>userKeyed || event.userId != null && linked.has(event.userId)).map((event)=>({
|
|
67
|
+
userId: String(event.userId ?? event.unitId),
|
|
68
|
+
value: event.value,
|
|
69
|
+
ts: event.ts
|
|
70
|
+
}));
|
|
71
|
+
const credited = creditUserEvents(units, identities, userEvents).events;
|
|
72
|
+
return userKeyed ? credited : [
|
|
73
|
+
...events.filter((event)=>event.userId == null || !linked.has(event.userId)),
|
|
74
|
+
...credited
|
|
75
|
+
];
|
|
76
|
+
};
|
|
77
|
+
const diagnostics = creditUserEvents(units, identities, []);
|
|
78
|
+
const metricData = [];
|
|
79
|
+
for (const definition of phase.metricDefinitions){
|
|
80
|
+
try {
|
|
81
|
+
if (Date.now() >= deadline) throw new ResultsBudgetError();
|
|
82
|
+
const kind = definition.kind;
|
|
83
|
+
const windowHours = phase.analysis.attributionWindowHours ?? definition.attributionWindowHours ?? 168;
|
|
84
|
+
const from = new Date([
|
|
85
|
+
...units.values()
|
|
86
|
+
].reduce((earliest, unit)=>Math.min(earliest, +unit.firstTs), timestampMs(phase.startedAt)));
|
|
87
|
+
const preFrom = new Date(+from - 14 * 86_400_000);
|
|
88
|
+
let events, covariates;
|
|
89
|
+
if (definition.source.startsWith('events:')) {
|
|
90
|
+
const eventName = definition.source.slice(7);
|
|
91
|
+
if (!options.eventSources?.includes(eventName)) throw new Error('Metric source unavailable');
|
|
92
|
+
const read = async (start)=>{
|
|
93
|
+
if (candidate.unitType === 'user') return metricEventRows(exec, {
|
|
94
|
+
eventName,
|
|
95
|
+
userIds: ids,
|
|
96
|
+
from: start,
|
|
97
|
+
to
|
|
98
|
+
});
|
|
99
|
+
const direct = await metricEventRows(exec, {
|
|
100
|
+
eventName,
|
|
101
|
+
unitIds: ids,
|
|
102
|
+
from: start,
|
|
103
|
+
to
|
|
104
|
+
});
|
|
105
|
+
const signedIn = await metricEventRows(exec, {
|
|
106
|
+
eventName,
|
|
107
|
+
userIds,
|
|
108
|
+
from: start,
|
|
109
|
+
to
|
|
110
|
+
});
|
|
111
|
+
return [
|
|
112
|
+
...direct,
|
|
113
|
+
...signedIn
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
events = credit(await read(phase.analysis.cuped === 'auto' ? preFrom : from), false);
|
|
117
|
+
if (phase.analysis.cuped === 'auto') {
|
|
118
|
+
const preUnits = new Map([
|
|
119
|
+
...units
|
|
120
|
+
].map(([id, unit])=>[
|
|
121
|
+
id,
|
|
122
|
+
{
|
|
123
|
+
arm: unit.arm,
|
|
124
|
+
firstTs: new Date(+unit.firstTs - 14 * 86_400_000)
|
|
125
|
+
}
|
|
126
|
+
]));
|
|
127
|
+
covariates = perUnitValues(preUnits, events, {
|
|
128
|
+
kind,
|
|
129
|
+
windowHours: 14 * 24
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
const source = options.metricSources?.find((source)=>source.id === definition.source);
|
|
134
|
+
if (!source || source.kind !== kind || definition.keyedBy && source.keyedBy !== definition.keyedBy) throw new Error('Metric source unavailable');
|
|
135
|
+
const userKeyed = source.keyedBy === 'user' && candidate.unitType === 'visitor';
|
|
136
|
+
if (candidate.unitType === 'user' && source.keyedBy !== 'user') throw new Error('Metric unit type unavailable');
|
|
137
|
+
const sourceIds = userKeyed ? userIds : ids;
|
|
138
|
+
events = credit(await raceDeadline(deadline, ()=>source.query({
|
|
139
|
+
from,
|
|
140
|
+
to,
|
|
141
|
+
unitIds: sourceIds
|
|
142
|
+
})), userKeyed);
|
|
143
|
+
if (phase.analysis.cuped === 'auto' && source.covariate) {
|
|
144
|
+
const sourceUnits = userKeyed ? creditUserEvents(units, identities, userIds.map((userId)=>({
|
|
145
|
+
userId,
|
|
146
|
+
ts: now,
|
|
147
|
+
value: 0
|
|
148
|
+
}))).events.map((event)=>({
|
|
149
|
+
unitId: identities.find((identity)=>identity.anonymousId === event.unitId).userId,
|
|
150
|
+
firstExposureTs: units.get(event.unitId).firstTs
|
|
151
|
+
})) : [
|
|
152
|
+
...units
|
|
153
|
+
].map(([unitId, unit])=>({
|
|
154
|
+
unitId,
|
|
155
|
+
firstExposureTs: unit.firstTs
|
|
156
|
+
}));
|
|
157
|
+
const pre = await raceDeadline(deadline, ()=>source.covariate({
|
|
158
|
+
units: sourceUnits,
|
|
159
|
+
lookbackDays: 14
|
|
160
|
+
}));
|
|
161
|
+
if (pre.some((row)=>!Number.isFinite(row.value))) throw new Error('Covariates must be finite');
|
|
162
|
+
const credited = credit(pre.map((row)=>({
|
|
163
|
+
...row,
|
|
164
|
+
ts: now
|
|
165
|
+
})), userKeyed);
|
|
166
|
+
covariates = new Map(credited.map((row)=>[
|
|
167
|
+
row.unitId,
|
|
168
|
+
row.value
|
|
169
|
+
]));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (events.some((event)=>!Number.isFinite(event.value))) throw new Error('Metric values must be finite');
|
|
173
|
+
metricData.push({
|
|
174
|
+
metricId: definition.metricId,
|
|
175
|
+
values: perUnitValues(units, events, {
|
|
176
|
+
kind,
|
|
177
|
+
windowHours,
|
|
178
|
+
endedAt: to
|
|
179
|
+
}),
|
|
180
|
+
...covariates ? {
|
|
181
|
+
covariates
|
|
182
|
+
} : {}
|
|
183
|
+
});
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const reason = error instanceof Error ? error.message : 'Metric source unavailable';
|
|
186
|
+
metricData.push({
|
|
187
|
+
metricId: definition.metricId,
|
|
188
|
+
values: new Map(),
|
|
189
|
+
unavailable: reason
|
|
190
|
+
});
|
|
191
|
+
if (unavailableMetrics.length < 100) unavailableMetrics.push({
|
|
192
|
+
experimentId: phase.experiment,
|
|
193
|
+
phase: phase.phase,
|
|
194
|
+
metricId: definition.metricId,
|
|
195
|
+
reason
|
|
196
|
+
});
|
|
197
|
+
if (error instanceof ResultsBudgetError || Date.now() >= deadline) throw new ResultsBudgetError();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const status = phase.endedAt != null || candidate.status === 'archived' ? 'stopped' : candidate.status;
|
|
201
|
+
const snapshot = analysePhase({
|
|
202
|
+
phase: analysisPhase,
|
|
203
|
+
now,
|
|
204
|
+
units,
|
|
205
|
+
metricData,
|
|
206
|
+
status,
|
|
207
|
+
excludedMultiArm: exposed.excludedMultiArm,
|
|
208
|
+
armMismatch: await armMismatchCount(exec, query),
|
|
209
|
+
identityConflictShare: userIds.length ? diagnostics.conflicts / userIds.length : 0,
|
|
210
|
+
...phase.phaseKind === 'rollout' ? {
|
|
211
|
+
winnerArm: phase.arms.find((arm)=>arm.kind !== 'holdout').key
|
|
212
|
+
} : {}
|
|
213
|
+
});
|
|
214
|
+
return snapshot;
|
|
215
|
+
}
|
|
216
|
+
export async function runResults(payload, { budgetMs = 20_000, experimentId, sweep = true } = {}) {
|
|
217
|
+
if (!Number.isFinite(budgetMs) || budgetMs < 0) throw new RangeError('budgetMs must be finite and nonnegative');
|
|
218
|
+
const started = Date.now(), deadline = started + budgetMs, now = new Date(started);
|
|
219
|
+
const summary = {
|
|
220
|
+
ran: 0,
|
|
221
|
+
skipped: 0,
|
|
222
|
+
swept: 0,
|
|
223
|
+
processedIds: [],
|
|
224
|
+
budgetExhausted: false,
|
|
225
|
+
unavailableMetrics: [],
|
|
226
|
+
sweepAttempted: false
|
|
227
|
+
};
|
|
228
|
+
if (budgetMs === 0) return {
|
|
229
|
+
...summary,
|
|
230
|
+
budgetExhausted: true
|
|
231
|
+
};
|
|
232
|
+
const options = payload.config.custom?.xp;
|
|
233
|
+
const retentionDays = options.events?.retentionDays ?? 180;
|
|
234
|
+
// Reserve up to one second (25% of short runs) for retention, even when host
|
|
235
|
+
// code never settles. The safety margin also leaves time to return the summary.
|
|
236
|
+
const analysisDeadline = deadline - (sweep ? Math.min(1000, budgetMs / 4) : 0) - RESULTS_TIMEOUT_MARGIN_MS;
|
|
237
|
+
const exec = resultsExecutor(payload, ()=>analysisDeadline - Date.now());
|
|
238
|
+
let candidates = [];
|
|
239
|
+
try {
|
|
240
|
+
candidates = await resultsCandidates(exec, {
|
|
241
|
+
cutoff: new Date(started - retentionDays * 86_400_000).toISOString(),
|
|
242
|
+
experimentId
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
summary.skipped++;
|
|
246
|
+
summary.budgetExhausted = error instanceof ResultsBudgetError;
|
|
247
|
+
payload.logger.error({
|
|
248
|
+
err: error,
|
|
249
|
+
experimentId
|
|
250
|
+
}, 'Experiment scheduling failed');
|
|
251
|
+
}
|
|
252
|
+
// Query order is oldest phase snapshot first; grouping keeps each experiment's
|
|
253
|
+
// phases together while preserving that scheduling priority.
|
|
254
|
+
const experiments = new Map();
|
|
255
|
+
for (const candidate of candidates){
|
|
256
|
+
const id = candidate.phase.experiment;
|
|
257
|
+
experiments.set(id, [
|
|
258
|
+
...experiments.get(id) ?? [],
|
|
259
|
+
candidate
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
for (const [id, phases] of experiments){
|
|
263
|
+
if (Date.now() >= analysisDeadline) {
|
|
264
|
+
summary.budgetExhausted = true;
|
|
265
|
+
summary.skipped += experiments.size - summary.ran - summary.skipped;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const snapshots = [];
|
|
270
|
+
for (const phase of phases){
|
|
271
|
+
if (Date.now() >= analysisDeadline) throw new ResultsBudgetError();
|
|
272
|
+
snapshots.push(await analyseCandidate(exec, phase, options, now, analysisDeadline, summary.unavailableMetrics));
|
|
273
|
+
}
|
|
274
|
+
if (Date.now() >= analysisDeadline) throw new ResultsBudgetError();
|
|
275
|
+
await writeSnapshots(exec, snapshots);
|
|
276
|
+
summary.ran++;
|
|
277
|
+
if (summary.processedIds.length < 100) summary.processedIds.push(id);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
summary.skipped++;
|
|
280
|
+
summary.budgetExhausted ||= error instanceof ResultsBudgetError;
|
|
281
|
+
payload.logger.error({
|
|
282
|
+
err: error,
|
|
283
|
+
experimentId: id
|
|
284
|
+
}, 'Experiment results failed');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (sweep) {
|
|
288
|
+
summary.sweepAttempted = true;
|
|
289
|
+
const sweepDeadline = deadline - RESULTS_TIMEOUT_MARGIN_MS;
|
|
290
|
+
try {
|
|
291
|
+
const sweepExec = resultsExecutor(payload, ()=>sweepDeadline - Date.now());
|
|
292
|
+
summary.swept = await sweepEvents(sweepExec, {
|
|
293
|
+
now,
|
|
294
|
+
retentionDays,
|
|
295
|
+
shouldContinue: ()=>Date.now() < sweepDeadline,
|
|
296
|
+
logger: payload.logger
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
summary.budgetExhausted ||= error instanceof ResultsBudgetError;
|
|
300
|
+
payload.logger.error({
|
|
301
|
+
err: error
|
|
302
|
+
}, 'Experiment retention sweep failed');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
summary.budgetExhausted ||= Date.now() >= deadline;
|
|
306
|
+
return summary;
|
|
307
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExperimentsProxyRuntime, ExperimentsServerRuntime, ExperimentsServerRuntimeOptions } from '../types/runtime.js';
|
|
2
|
+
export declare function hasHostAttributesOf(runtime: object): boolean;
|
|
3
|
+
export declare function defineExperimentsServerRuntime<TUser extends {
|
|
4
|
+
id: string | number;
|
|
5
|
+
} = {
|
|
6
|
+
id: string | number;
|
|
7
|
+
}>(proxyRuntime: ExperimentsProxyRuntime, opts: ExperimentsServerRuntimeOptions<TUser>): ExperimentsServerRuntime<TUser>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const hostAttributesOf = Symbol('hostAttributesOf');
|
|
2
|
+
export function hasHostAttributesOf(runtime) {
|
|
3
|
+
return Object.getOwnPropertyDescriptor(runtime, hostAttributesOf)?.value === true;
|
|
4
|
+
}
|
|
5
|
+
export function defineExperimentsServerRuntime(proxyRuntime, opts) {
|
|
6
|
+
const runtime = {
|
|
7
|
+
proxyRuntime,
|
|
8
|
+
getPayload: opts.getPayload,
|
|
9
|
+
getUser: opts.getUser,
|
|
10
|
+
userIdOf: opts.userIdOf ?? ((u)=>String(u.id)),
|
|
11
|
+
attributesOf: opts.attributesOf ?? ((u)=>({
|
|
12
|
+
loggedIn: Boolean(u)
|
|
13
|
+
}))
|
|
14
|
+
};
|
|
15
|
+
if (typeof opts.attributesOf === 'function') Object.defineProperty(runtime, hostAttributesOf, {
|
|
16
|
+
value: true,
|
|
17
|
+
enumerable: false
|
|
18
|
+
});
|
|
19
|
+
return runtime;
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { findNormalisedPathKey, normalisePath } from '../runtime/normalisePath.js';
|
|
2
|
+
/** Build-time filter: pass a server-loaded manifest when generating the sitemap. */ export function experimentSitemapFilter(path, manifest) {
|
|
3
|
+
const live = manifest.experiments.find((e)=>e.kind === 'page' && e.arms.some((a)=>a.pageTarget !== undefined && normalisePath(a.pageTarget) === normalisePath(path)));
|
|
4
|
+
if (live) return live.arms.some((a)=>a.kind === 'control' && a.pageTarget !== undefined && normalisePath(a.pageTarget) === normalisePath(path));
|
|
5
|
+
return findNormalisedPathKey(manifest.archivedPages, path) === undefined;
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Assignments, ClientAssignments } from '../types/assignments.js';
|
|
2
|
+
export declare function toClientAssignments(assignments: Assignments, opts: {
|
|
3
|
+
pathname: string;
|
|
4
|
+
apiRoute?: string;
|
|
5
|
+
flagPoints?: string[];
|
|
6
|
+
experiments?: string[];
|
|
7
|
+
}): ClientAssignments;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { normalisePath } from '../runtime/normalisePath.js';
|
|
2
|
+
import { manifestOf, suppressExposure } from './context.js';
|
|
3
|
+
export function toClientAssignments(assignments, opts) {
|
|
4
|
+
const blocked = suppressExposure(assignments);
|
|
5
|
+
const manifest = manifestOf(assignments);
|
|
6
|
+
const experiments = Object.fromEntries((opts.experiments ?? []).filter((slug)=>Object.hasOwn(assignments.bySlug, slug)).map((slug)=>{
|
|
7
|
+
const a = assignments.bySlug[slug];
|
|
8
|
+
const experiment = manifest?.experiments.find((e)=>e.slug === slug);
|
|
9
|
+
const arm = experiment?.arms.find((arm)=>arm.key === a.arm && arm.kind !== 'holdout') ?? experiment?.arms.find((arm)=>arm.kind === 'control');
|
|
10
|
+
return [
|
|
11
|
+
slug,
|
|
12
|
+
{
|
|
13
|
+
arm: a.arm,
|
|
14
|
+
experimentId: a.experimentId,
|
|
15
|
+
phase: a.phase,
|
|
16
|
+
params: {
|
|
17
|
+
...arm?.params
|
|
18
|
+
},
|
|
19
|
+
policy: a.policy,
|
|
20
|
+
exposable: a.exposable && !blocked
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
}));
|
|
24
|
+
const flagPoints = Object.fromEntries((opts.flagPoints ?? []).filter((id)=>Object.hasOwn(assignments.flagPoints, id)).map((id)=>{
|
|
25
|
+
const point = assignments.flagPoints[id];
|
|
26
|
+
return [
|
|
27
|
+
id,
|
|
28
|
+
{
|
|
29
|
+
...point,
|
|
30
|
+
params: {
|
|
31
|
+
...point.params
|
|
32
|
+
},
|
|
33
|
+
exposable: point.exposable && !blocked
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
}));
|
|
37
|
+
const result = {
|
|
38
|
+
v: 1,
|
|
39
|
+
revision: assignments.revision,
|
|
40
|
+
apiRoute: opts.apiRoute ?? '/api',
|
|
41
|
+
experiments,
|
|
42
|
+
flagPoints,
|
|
43
|
+
pageviews: []
|
|
44
|
+
};
|
|
45
|
+
if (!blocked) for (const e of manifest?.experiments ?? []){
|
|
46
|
+
const a = assignments.bySlug[e.slug];
|
|
47
|
+
if (e.kind !== 'page' || !a?.exposable) continue;
|
|
48
|
+
const served = e.arms.find((arm)=>arm.key === a.arm && arm.kind !== 'holdout') ?? e.arms.find((arm)=>arm.kind === 'control');
|
|
49
|
+
if (served?.pageTarget !== undefined && normalisePath(served.pageTarget) === normalisePath(opts.pathname)) result.pageviews.push({
|
|
50
|
+
slug: e.slug,
|
|
51
|
+
arm: a.arm,
|
|
52
|
+
phase: a.phase
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExperimentsServerRuntime } from '../types/runtime.js';
|
|
2
|
+
export declare function trackConversion<TUser extends {
|
|
3
|
+
id: string | number;
|
|
4
|
+
} = {
|
|
5
|
+
id: string | number;
|
|
6
|
+
}>(serverRuntime: ExperimentsServerRuntime<TUser>, event: {
|
|
7
|
+
eventName: string;
|
|
8
|
+
value?: number;
|
|
9
|
+
idempotencyKey: string;
|
|
10
|
+
userId?: string;
|
|
11
|
+
headers?: Headers;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server.js';
|
|
2
|
+
import { pooledExecutor } from '../db/executor.js';
|
|
3
|
+
import { insertEvents } from '../db/events.js';
|
|
4
|
+
import { readUnitCookie } from '../runtime/cookie.js';
|
|
5
|
+
export async function trackConversion(serverRuntime, event) {
|
|
6
|
+
let payload;
|
|
7
|
+
try {
|
|
8
|
+
let headers = event.headers;
|
|
9
|
+
if (!headers) {
|
|
10
|
+
try {
|
|
11
|
+
headers = await (await import('next/headers.js')).headers();
|
|
12
|
+
} catch {}
|
|
13
|
+
}
|
|
14
|
+
const runtime = serverRuntime.proxyRuntime;
|
|
15
|
+
// Consent sees cookies and headers only; the URL is a placeholder.
|
|
16
|
+
let consent = true;
|
|
17
|
+
try {
|
|
18
|
+
consent = runtime.cookie.consent?.(new NextRequest('http://consent.invalid/', {
|
|
19
|
+
headers
|
|
20
|
+
})) !== false;
|
|
21
|
+
} catch {
|
|
22
|
+
console.error('[payload-experiments] consent failed; treating consent as false');
|
|
23
|
+
consent = false;
|
|
24
|
+
}
|
|
25
|
+
if (!consent) return;
|
|
26
|
+
payload = await serverRuntime.getPayload();
|
|
27
|
+
if (typeof event.idempotencyKey !== 'string' || !event.idempotencyKey.trim()) {
|
|
28
|
+
try {
|
|
29
|
+
payload.logger.warn('Experiments: trackConversion requires a non-empty idempotencyKey');
|
|
30
|
+
} catch {}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const visitorId = headers ? readUnitCookie(headers, runtime) : null;
|
|
34
|
+
let userId = event.userId;
|
|
35
|
+
if (!userId && serverRuntime.getUser) {
|
|
36
|
+
try {
|
|
37
|
+
const user = await serverRuntime.getUser();
|
|
38
|
+
if (user) userId = serverRuntime.userIdOf(user);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
try {
|
|
41
|
+
payload.logger.error({
|
|
42
|
+
err: error,
|
|
43
|
+
msg: 'Experiments: trackConversion getUser failed'
|
|
44
|
+
});
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!visitorId && !userId) return;
|
|
49
|
+
await insertEvents(pooledExecutor(payload), [
|
|
50
|
+
{
|
|
51
|
+
type: 'conversion',
|
|
52
|
+
eventName: event.eventName,
|
|
53
|
+
value: event.value ?? 1,
|
|
54
|
+
unitId: visitorId ?? userId,
|
|
55
|
+
unitType: visitorId ? 'visitor' : 'user',
|
|
56
|
+
userId,
|
|
57
|
+
dedupeKey: `conversion:s:${event.idempotencyKey}`,
|
|
58
|
+
ts: new Date().toISOString()
|
|
59
|
+
}
|
|
60
|
+
], (message)=>payload.logger.error(message));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
try {
|
|
63
|
+
payload?.logger.error({
|
|
64
|
+
err: error,
|
|
65
|
+
msg: 'Experiments: trackConversion failed'
|
|
66
|
+
});
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const TARGETING_OPS: {
|
|
2
|
+
readonly string: readonly ["eq", "neq", "in", "nin", "contains"];
|
|
3
|
+
readonly number: readonly ["eq", "neq", "gt", "gte", "lt", "lte"];
|
|
4
|
+
readonly boolean: readonly ["eq"];
|
|
5
|
+
};
|
|
6
|
+
export type TargetingOp = (typeof TARGETING_OPS)[keyof typeof TARGETING_OPS][number];
|
|
7
|
+
export declare function isOpValid(type: string, op: string): op is TargetingOp;
|
|
8
|
+
export declare function evaluateOp(type: string, op: string, actual: unknown, expected: unknown): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const TARGETING_OPS = {
|
|
2
|
+
string: [
|
|
3
|
+
'eq',
|
|
4
|
+
'neq',
|
|
5
|
+
'in',
|
|
6
|
+
'nin',
|
|
7
|
+
'contains'
|
|
8
|
+
],
|
|
9
|
+
number: [
|
|
10
|
+
'eq',
|
|
11
|
+
'neq',
|
|
12
|
+
'gt',
|
|
13
|
+
'gte',
|
|
14
|
+
'lt',
|
|
15
|
+
'lte'
|
|
16
|
+
],
|
|
17
|
+
boolean: [
|
|
18
|
+
'eq'
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
export function isOpValid(type, op) {
|
|
22
|
+
return Object.hasOwn(TARGETING_OPS, type) && TARGETING_OPS[type].includes(op);
|
|
23
|
+
}
|
|
24
|
+
export function evaluateOp(type, op, actual, expected) {
|
|
25
|
+
if (!isOpValid(type, op) || typeof actual !== type) return false;
|
|
26
|
+
if (type === 'string') {
|
|
27
|
+
const value = actual.toLowerCase();
|
|
28
|
+
if (op === 'in' || op === 'nin') {
|
|
29
|
+
if (!Array.isArray(expected) || !expected.every((v)=>typeof v === 'string')) return false;
|
|
30
|
+
const found = expected.some((v)=>v.toLowerCase() === value);
|
|
31
|
+
return op === 'in' ? found : !found;
|
|
32
|
+
}
|
|
33
|
+
if (typeof expected !== 'string') return false;
|
|
34
|
+
const target = expected.toLowerCase();
|
|
35
|
+
if (op === 'contains') return value.includes(target);
|
|
36
|
+
return op === 'eq' ? value === target : value !== target;
|
|
37
|
+
}
|
|
38
|
+
if (typeof expected !== type) return false;
|
|
39
|
+
if (type === 'boolean') return actual === expected;
|
|
40
|
+
if (type !== 'number' || !Number.isFinite(actual) || !Number.isFinite(expected)) return false;
|
|
41
|
+
const a = actual, b = expected;
|
|
42
|
+
switch(op){
|
|
43
|
+
case 'eq':
|
|
44
|
+
return a === b;
|
|
45
|
+
case 'neq':
|
|
46
|
+
return a !== b;
|
|
47
|
+
case 'gt':
|
|
48
|
+
return a > b;
|
|
49
|
+
case 'gte':
|
|
50
|
+
return a >= b;
|
|
51
|
+
case 'lt':
|
|
52
|
+
return a < b;
|
|
53
|
+
case 'lte':
|
|
54
|
+
return a <= b;
|
|
55
|
+
default:
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { DocId, ResolvedOptions } from '../types/contracts.js';
|
|
3
|
+
import type { AddMetricBody } from '../types/endpoints.js';
|
|
4
|
+
export declare function addMetric<TUser extends {
|
|
5
|
+
id: DocId;
|
|
6
|
+
}>(req: PayloadRequest, { experimentId, metric, role, options }: AddMetricBody & {
|
|
7
|
+
experimentId: DocId;
|
|
8
|
+
options: ResolvedOptions<TUser>;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
detail?: {
|
|
11
|
+
metricId: DocId;
|
|
12
|
+
message: string;
|
|
13
|
+
}[] | undefined;
|
|
14
|
+
doc: import("payload").JsonObject & import("payload").TypeWithID;
|
|
15
|
+
revision: number;
|
|
16
|
+
}>;
|