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