@delmaredigital/payload-experiments 0.0.0-stage → 0.1.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +590 -2
- package/dist/access.d.ts +1 -0
- package/dist/access.js +2 -0
- package/dist/admin/AllocationField.d.ts +2 -0
- package/dist/admin/AllocationField.js +80 -0
- package/dist/admin/FlagPointSelect.d.ts +2 -0
- package/dist/admin/FlagPointSelect.js +54 -0
- package/dist/admin/LayerUsage.d.ts +2 -0
- package/dist/admin/LayerUsage.js +24 -0
- package/dist/admin/LockedFieldNotice.d.ts +2 -0
- package/dist/admin/LockedFieldNotice.js +26 -0
- package/dist/admin/MetricSourceSelect.d.ts +2 -0
- package/dist/admin/MetricSourceSelect.js +32 -0
- package/dist/admin/PhaseHistory.d.ts +2 -0
- package/dist/admin/PhaseHistory.js +76 -0
- package/dist/admin/PreviewLinks.d.ts +11 -0
- package/dist/admin/PreviewLinks.js +69 -0
- package/dist/admin/StatusCell.d.ts +3 -0
- package/dist/admin/StatusCell.js +19 -0
- package/dist/admin/SummaryCell.d.ts +2 -0
- package/dist/admin/SummaryCell.js +32 -0
- package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
- package/dist/admin/actions/AddMetricDialog.js +138 -0
- package/dist/admin/actions/AllocateDialog.d.ts +6 -0
- package/dist/admin/actions/AllocateDialog.js +81 -0
- package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
- package/dist/admin/actions/ConfirmDialog.js +192 -0
- package/dist/admin/actions/DecideDialog.d.ts +7 -0
- package/dist/admin/actions/DecideDialog.js +173 -0
- package/dist/admin/actions/ExperimentActions.d.ts +3 -0
- package/dist/admin/actions/ExperimentActions.js +110 -0
- package/dist/admin/actions/MakeContentResult.d.ts +6 -0
- package/dist/admin/actions/MakeContentResult.js +50 -0
- package/dist/admin/actions/actions.model.d.ts +37 -0
- package/dist/admin/actions/actions.model.js +71 -0
- package/dist/admin/actions/decide.model.d.ts +21 -0
- package/dist/admin/actions/decide.model.js +38 -0
- package/dist/admin/allocation.model.d.ts +20 -0
- package/dist/admin/allocation.model.js +38 -0
- package/dist/admin/api.d.ts +32 -0
- package/dist/admin/api.js +95 -0
- package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
- package/dist/admin/armSelect/ArmSelectField.js +185 -0
- package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
- package/dist/admin/armSelect/armSelect.model.js +20 -0
- package/dist/admin/arms/ArmsField.d.ts +2 -0
- package/dist/admin/arms/ArmsField.js +310 -0
- package/dist/admin/arms/ParamsEditor.d.ts +16 -0
- package/dist/admin/arms/ParamsEditor.js +79 -0
- package/dist/admin/arms/arms.model.d.ts +21 -0
- package/dist/admin/arms/arms.model.js +153 -0
- package/dist/admin/arms/pageTarget.model.d.ts +4 -0
- package/dist/admin/arms/pageTarget.model.js +19 -0
- package/dist/admin/arms/params.model.d.ts +7 -0
- package/dist/admin/arms/params.model.js +35 -0
- package/dist/admin/copy/actions.d.ts +38 -0
- package/dist/admin/copy/actions.js +38 -0
- package/dist/admin/copy/armSelect.d.ts +11 -0
- package/dist/admin/copy/armSelect.js +11 -0
- package/dist/admin/copy/arms.d.ts +37 -0
- package/dist/admin/copy/arms.js +47 -0
- package/dist/admin/copy/common.d.ts +20 -0
- package/dist/admin/copy/common.js +27 -0
- package/dist/admin/copy/history.d.ts +9 -0
- package/dist/admin/copy/history.js +9 -0
- package/dist/admin/copy/metrics.d.ts +9 -0
- package/dist/admin/copy/metrics.js +9 -0
- package/dist/admin/copy/results.d.ts +78 -0
- package/dist/admin/copy/results.js +81 -0
- package/dist/admin/copy/review.d.ts +33 -0
- package/dist/admin/copy/review.js +127 -0
- package/dist/admin/copy/status.d.ts +8 -0
- package/dist/admin/copy/status.js +8 -0
- package/dist/admin/copy/tabs.d.ts +7 -0
- package/dist/admin/copy/tabs.js +7 -0
- package/dist/admin/copy/who.d.ts +36 -0
- package/dist/admin/copy/who.js +36 -0
- package/dist/admin/labels.d.ts +45 -0
- package/dist/admin/labels.js +53 -0
- package/dist/admin/layerUsage.model.d.ts +15 -0
- package/dist/admin/layerUsage.model.js +18 -0
- package/dist/admin/lockedNotice.model.d.ts +3 -0
- package/dist/admin/lockedNotice.model.js +18 -0
- package/dist/admin/metricSource.model.d.ts +7 -0
- package/dist/admin/metricSource.model.js +11 -0
- package/dist/admin/paths.d.ts +16 -0
- package/dist/admin/paths.js +16 -0
- package/dist/admin/permissions.model.d.ts +10 -0
- package/dist/admin/permissions.model.js +4 -0
- package/dist/admin/phaseHistory.model.d.ts +19 -0
- package/dist/admin/phaseHistory.model.js +19 -0
- package/dist/admin/reload.d.ts +1 -0
- package/dist/admin/reload.js +1 -0
- package/dist/admin/results/ArmTable.d.ts +8 -0
- package/dist/admin/results/ArmTable.js +112 -0
- package/dist/admin/results/CappedCell.d.ts +4 -0
- package/dist/admin/results/CappedCell.js +24 -0
- package/dist/admin/results/CiChart.d.ts +10 -0
- package/dist/admin/results/CiChart.js +178 -0
- package/dist/admin/results/DataQualityPanel.d.ts +4 -0
- package/dist/admin/results/DataQualityPanel.js +40 -0
- package/dist/admin/results/ResultsView.d.ts +3 -0
- package/dist/admin/results/ResultsView.js +312 -0
- package/dist/admin/results/VerdictCard.d.ts +9 -0
- package/dist/admin/results/VerdictCard.js +49 -0
- package/dist/admin/results/chart.model.d.ts +32 -0
- package/dist/admin/results/chart.model.js +52 -0
- package/dist/admin/results/format.d.ts +10 -0
- package/dist/admin/results/format.js +55 -0
- package/dist/admin/results/results.model.d.ts +105 -0
- package/dist/admin/results/results.model.js +175 -0
- package/dist/admin/review/ReviewPanel.d.ts +2 -0
- package/dist/admin/review/ReviewPanel.js +268 -0
- package/dist/admin/review/review.model.d.ts +31 -0
- package/dist/admin/review/review.model.js +40 -0
- package/dist/admin/status.model.d.ts +16 -0
- package/dist/admin/status.model.js +17 -0
- package/dist/admin/styles.css +10 -0
- package/dist/admin/styles.d.ts +1 -0
- package/dist/admin/styles.js +2 -0
- package/dist/admin/summaryBatcher.d.ts +8 -0
- package/dist/admin/summaryBatcher.js +33 -0
- package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
- package/dist/admin/targeting/TargetingAttributeField.js +31 -0
- package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
- package/dist/admin/targeting/TargetingOpField.js +45 -0
- package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
- package/dist/admin/targeting/TargetingValueField.js +142 -0
- package/dist/admin/targeting/targeting.model.d.ts +18 -0
- package/dist/admin/targeting/targeting.model.js +48 -0
- package/dist/admin/useApi.d.ts +20 -0
- package/dist/admin/useApi.js +10 -0
- package/dist/admin/useLayerUsage.d.ts +15 -0
- package/dist/admin/useLayerUsage.js +92 -0
- package/dist/client/ExperimentPageview.d.ts +1 -0
- package/dist/client/ExperimentPageview.js +27 -0
- package/dist/client/ExperimentsProvider.d.ts +6 -0
- package/dist/client/ExperimentsProvider.js +22 -0
- package/dist/client/ViewportExposure.d.ts +9 -0
- package/dist/client/ViewportExposure.js +69 -0
- package/dist/client/beacon.d.ts +17 -0
- package/dist/client/beacon.js +71 -0
- package/dist/client/context.d.ts +2 -0
- package/dist/client/context.js +2 -0
- package/dist/client/idempotency.d.ts +2 -0
- package/dist/client/idempotency.js +13 -0
- package/dist/client/trackConversionBeacon.d.ts +3 -0
- package/dist/client/trackConversionBeacon.js +16 -0
- package/dist/client/useExperiment.d.ts +5 -0
- package/dist/client/useExperiment.js +27 -0
- package/dist/client/useFlagPoint.d.ts +9 -0
- package/dist/client/useFlagPoint.js +32 -0
- package/dist/collections/access.d.ts +21 -0
- package/dist/collections/access.js +25 -0
- package/dist/collections/events.d.ts +3 -0
- package/dist/collections/events.js +117 -0
- package/dist/collections/experiments/armInvariants.d.ts +12 -0
- package/dist/collections/experiments/armInvariants.js +89 -0
- package/dist/collections/experiments/config.d.ts +3 -0
- package/dist/collections/experiments/config.js +574 -0
- package/dist/collections/experiments/createDefaults.d.ts +2 -0
- package/dist/collections/experiments/createDefaults.js +7 -0
- package/dist/collections/experiments/deleteGuard.d.ts +3 -0
- package/dist/collections/experiments/deleteGuard.js +8 -0
- package/dist/collections/experiments/editability.d.ts +4 -0
- package/dist/collections/experiments/editability.js +35 -0
- package/dist/collections/experiments/flagControlParams.d.ts +2 -0
- package/dist/collections/experiments/flagControlParams.js +32 -0
- package/dist/collections/experiments/lockHook.d.ts +2 -0
- package/dist/collections/experiments/lockHook.js +134 -0
- package/dist/collections/identities.d.ts +3 -0
- package/dist/collections/identities.js +30 -0
- package/dist/collections/index.d.ts +6 -0
- package/dist/collections/index.js +24 -0
- package/dist/collections/layers.d.ts +3 -0
- package/dist/collections/layers.js +77 -0
- package/dist/collections/metrics.d.ts +3 -0
- package/dist/collections/metrics.js +115 -0
- package/dist/collections/phases.d.ts +3 -0
- package/dist/collections/phases.js +59 -0
- package/dist/collections/serialize.d.ts +5 -0
- package/dist/collections/serialize.js +40 -0
- package/dist/collections/snapshots.d.ts +3 -0
- package/dist/collections/snapshots.js +47 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/db/adapter.d.ts +3 -0
- package/dist/db/adapter.js +9 -0
- package/dist/db/appends.d.ts +58 -0
- package/dist/db/appends.js +116 -0
- package/dist/db/delete.d.ts +17 -0
- package/dist/db/delete.js +35 -0
- package/dist/db/erase.d.ts +12 -0
- package/dist/db/erase.js +55 -0
- package/dist/db/events.d.ts +21 -0
- package/dist/db/events.js +100 -0
- package/dist/db/executor.d.ts +10 -0
- package/dist/db/executor.js +37 -0
- package/dist/db/identities.d.ts +11 -0
- package/dist/db/identities.js +29 -0
- package/dist/db/locks.d.ts +15 -0
- package/dist/db/locks.js +23 -0
- package/dist/db/manifest.d.ts +8 -0
- package/dist/db/manifest.js +8 -0
- package/dist/db/names.d.ts +271 -0
- package/dist/db/names.js +270 -0
- package/dist/db/pgExecutor.d.ts +41 -0
- package/dist/db/pgExecutor.js +250 -0
- package/dist/db/results.d.ts +53 -0
- package/dist/db/results.js +103 -0
- package/dist/db/slices.d.ts +18 -0
- package/dist/db/slices.js +51 -0
- package/dist/db/snapshots.d.ts +47 -0
- package/dist/db/snapshots.js +89 -0
- package/dist/db/sql.d.ts +5 -0
- package/dist/db/sql.js +5 -0
- package/dist/db/state.d.ts +12 -0
- package/dist/db/state.js +27 -0
- package/dist/db/sweep.d.ts +13 -0
- package/dist/db/sweep.js +90 -0
- package/dist/db/txid.d.ts +7 -0
- package/dist/db/txid.js +12 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +6 -0
- package/dist/endpoints/events.d.ts +7 -0
- package/dist/endpoints/events.js +98 -0
- package/dist/endpoints/index.d.ts +18 -0
- package/dist/endpoints/index.js +160 -0
- package/dist/endpoints/manifest.d.ts +7 -0
- package/dist/endpoints/manifest.js +30 -0
- package/dist/endpoints/preflight.d.ts +8 -0
- package/dist/endpoints/preflight.js +25 -0
- package/dist/endpoints/previewToken.d.ts +7 -0
- package/dist/endpoints/previewToken.js +54 -0
- package/dist/endpoints/recompute.d.ts +7 -0
- package/dist/endpoints/recompute.js +41 -0
- package/dist/endpoints/results.d.ts +24 -0
- package/dist/endpoints/results.js +136 -0
- package/dist/endpoints/summary.d.ts +7 -0
- package/dist/endpoints/summary.js +40 -0
- package/dist/endpoints/transitions.d.ts +42 -0
- package/dist/endpoints/transitions.js +154 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +33 -0
- package/dist/events/body.d.ts +7 -0
- package/dist/events/body.js +43 -0
- package/dist/events/dimensions.d.ts +4 -0
- package/dist/events/dimensions.js +51 -0
- package/dist/events/preview.d.ts +2 -0
- package/dist/events/preview.js +9 -0
- package/dist/events/request.d.ts +3 -0
- package/dist/events/request.js +14 -0
- package/dist/events/rows.d.ts +7 -0
- package/dist/events/rows.js +65 -0
- package/dist/exports/adminClient.d.ts +16 -0
- package/dist/exports/adminClient.js +17 -0
- package/dist/exports/client.d.ts +7 -0
- package/dist/exports/client.js +7 -0
- package/dist/exports/index.d.ts +4 -0
- package/dist/exports/index.js +3 -0
- package/dist/exports/proxy.d.ts +4 -0
- package/dist/exports/proxy.js +3 -0
- package/dist/exports/puck.d.ts +4 -0
- package/dist/exports/puck.js +3 -0
- package/dist/exports/puckEditor.d.ts +1 -0
- package/dist/exports/puckEditor.js +2 -0
- package/dist/exports/rsc.d.ts +13 -0
- package/dist/exports/rsc.js +13 -0
- package/dist/exports/runtimeProxy.d.ts +4 -0
- package/dist/exports/runtimeProxy.js +2 -0
- package/dist/exports/runtimeServer.d.ts +2 -0
- package/dist/exports/runtimeServer.js +1 -0
- package/dist/fields/armSelect.d.ts +7 -0
- package/dist/fields/armSelect.js +33 -0
- package/dist/globals/state.d.ts +3 -0
- package/dist/globals/state.js +31 -0
- package/dist/hooks/contentChanges.d.ts +3 -0
- package/dist/hooks/contentChanges.js +10 -0
- package/dist/hooks/hostHook.d.ts +5 -0
- package/dist/hooks/hostHook.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/manifest/cache.d.ts +10 -0
- package/dist/manifest/cache.js +51 -0
- package/dist/manifest/compile.d.ts +63 -0
- package/dist/manifest/compile.js +237 -0
- package/dist/onInit.d.ts +5 -0
- package/dist/onInit.js +13 -0
- package/dist/options/fromReq.d.ts +3 -0
- package/dist/options/fromReq.js +5 -0
- package/dist/options.d.ts +9 -0
- package/dist/options.js +107 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +62 -0
- package/dist/proxy/apply.d.ts +3 -0
- package/dist/proxy/apply.js +5 -0
- package/dist/proxy/consentFallback.d.ts +5 -0
- package/dist/proxy/consentFallback.js +27 -0
- package/dist/proxy/control.d.ts +6 -0
- package/dist/proxy/control.js +36 -0
- package/dist/proxy/createProxy.d.ts +3 -0
- package/dist/proxy/createProxy.js +15 -0
- package/dist/proxy/pageRewrite.d.ts +4 -0
- package/dist/proxy/pageRewrite.js +41 -0
- package/dist/proxy/resolve.d.ts +13 -0
- package/dist/proxy/resolve.js +163 -0
- package/dist/proxy/skip.d.ts +1 -0
- package/dist/proxy/skip.js +9 -0
- package/dist/puck/ExperimentPicker.d.ts +5 -0
- package/dist/puck/ExperimentPicker.js +88 -0
- package/dist/puck/ExperimentSlot.d.ts +3 -0
- package/dist/puck/ExperimentSlot.js +16 -0
- package/dist/puck/copy.d.ts +14 -0
- package/dist/puck/copy.js +14 -0
- package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
- package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
- package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
- package/dist/puck/editor/cloneSlot.d.ts +3 -0
- package/dist/puck/editor/cloneSlot.js +41 -0
- package/dist/puck/editor/copy.d.ts +25 -0
- package/dist/puck/editor/copy.js +25 -0
- package/dist/puck/editor/editor.model.d.ts +16 -0
- package/dist/puck/editor/editor.model.js +33 -0
- package/dist/puck/experimentSlotField.d.ts +5 -0
- package/dist/puck/experimentSlotField.js +13 -0
- package/dist/puck/slot.model.d.ts +2 -0
- package/dist/puck/slot.model.js +11 -0
- package/dist/puck/slotConfig.d.ts +5 -0
- package/dist/puck/slotConfig.js +25 -0
- package/dist/results/aggregate.d.ts +34 -0
- package/dist/results/aggregate.js +66 -0
- package/dist/results/analyse.d.ts +79 -0
- package/dist/results/analyse.js +378 -0
- package/dist/results/cuped.d.ts +16 -0
- package/dist/results/cuped.js +31 -0
- package/dist/results/decideGuard.d.ts +3 -0
- package/dist/results/decideGuard.js +8 -0
- package/dist/results/guardrails.d.ts +17 -0
- package/dist/results/guardrails.js +10 -0
- package/dist/results/history.d.ts +13 -0
- package/dist/results/history.js +39 -0
- package/dist/results/identityCredit.d.ts +16 -0
- package/dist/results/identityCredit.js +46 -0
- package/dist/results/power.d.ts +15 -0
- package/dist/results/power.js +60 -0
- package/dist/results/rolloutUnits.d.ts +17 -0
- package/dist/results/rolloutUnits.js +26 -0
- package/dist/results/snapshot.d.ts +9 -0
- package/dist/results/snapshot.js +154 -0
- package/dist/results/srm.d.ts +9 -0
- package/dist/results/srm.js +22 -0
- package/dist/results/verdict.d.ts +82 -0
- package/dist/results/verdict.js +85 -0
- package/dist/runtime/assign.d.ts +23 -0
- package/dist/runtime/assign.js +104 -0
- package/dist/runtime/bot.d.ts +1 -0
- package/dist/runtime/bot.js +7 -0
- package/dist/runtime/cookie.d.ts +17 -0
- package/dist/runtime/cookie.js +35 -0
- package/dist/runtime/eligibility.d.ts +22 -0
- package/dist/runtime/eligibility.js +20 -0
- package/dist/runtime/header.d.ts +17 -0
- package/dist/runtime/header.js +97 -0
- package/dist/runtime/loaders.d.ts +17 -0
- package/dist/runtime/loaders.js +29 -0
- package/dist/runtime/normalisePath.d.ts +7 -0
- package/dist/runtime/normalisePath.js +15 -0
- package/dist/runtime/previewToken.d.ts +9 -0
- package/dist/runtime/previewToken.js +23 -0
- package/dist/runtime/proxyRuntime.d.ts +2 -0
- package/dist/runtime/proxyRuntime.js +22 -0
- package/dist/runtime/seed.d.ts +2 -0
- package/dist/runtime/seed.js +4 -0
- package/dist/runtime/signing.d.ts +3 -0
- package/dist/runtime/signing.js +16 -0
- package/dist/runtime/targeting.d.ts +6 -0
- package/dist/runtime/targeting.js +9 -0
- package/dist/server/context.d.ts +10 -0
- package/dist/server/context.js +59 -0
- package/dist/server/eraseUser.d.ts +7 -0
- package/dist/server/eraseUser.js +4 -0
- package/dist/server/getAssignments.d.ts +9 -0
- package/dist/server/getAssignments.js +227 -0
- package/dist/server/getFlagPoint.d.ts +11 -0
- package/dist/server/getFlagPoint.js +36 -0
- package/dist/server/headerRevision.d.ts +6 -0
- package/dist/server/headerRevision.js +10 -0
- package/dist/server/linkIdentity.d.ts +9 -0
- package/dist/server/linkIdentity.js +34 -0
- package/dist/server/metadata.d.ts +7 -0
- package/dist/server/metadata.js +24 -0
- package/dist/server/previewDraft.d.ts +7 -0
- package/dist/server/previewDraft.js +51 -0
- package/dist/server/recordContentChange.d.ts +7 -0
- package/dist/server/recordContentChange.js +150 -0
- package/dist/server/recordExposures.d.ts +7 -0
- package/dist/server/recordExposures.js +56 -0
- package/dist/server/request.d.ts +7 -0
- package/dist/server/request.js +39 -0
- package/dist/server/resolveExperimentData.d.ts +6 -0
- package/dist/server/resolveExperimentData.js +64 -0
- package/dist/server/runResults.d.ts +22 -0
- package/dist/server/runResults.js +307 -0
- package/dist/server/serverRuntime.d.ts +7 -0
- package/dist/server/serverRuntime.js +20 -0
- package/dist/server/sitemap.d.ts +3 -0
- package/dist/server/sitemap.js +6 -0
- package/dist/server/toClientAssignments.d.ts +7 -0
- package/dist/server/toClientAssignments.js +56 -0
- package/dist/server/trackConversion.d.ts +12 -0
- package/dist/server/trackConversion.js +69 -0
- package/dist/targeting/ops.d.ts +8 -0
- package/dist/targeting/ops.js +58 -0
- package/dist/transitions/addMetric.d.ts +16 -0
- package/dist/transitions/addMetric.js +126 -0
- package/dist/transitions/allocate.d.ts +9 -0
- package/dist/transitions/allocate.js +84 -0
- package/dist/transitions/archive.d.ts +9 -0
- package/dist/transitions/archive.js +46 -0
- package/dist/transitions/checkStart.d.ts +61 -0
- package/dist/transitions/checkStart.js +104 -0
- package/dist/transitions/confirmPhrases.d.ts +5 -0
- package/dist/transitions/confirmPhrases.js +18 -0
- package/dist/transitions/decide.d.ts +9 -0
- package/dist/transitions/decide.js +138 -0
- package/dist/transitions/deleteExperiment.d.ts +5 -0
- package/dist/transitions/deleteExperiment.js +66 -0
- package/dist/transitions/kernel.d.ts +31 -0
- package/dist/transitions/kernel.js +69 -0
- package/dist/transitions/makeContent.d.ts +20 -0
- package/dist/transitions/makeContent.js +411 -0
- package/dist/transitions/phaseRecord.d.ts +27 -0
- package/dist/transitions/phaseRecord.js +81 -0
- package/dist/transitions/placements.d.ts +26 -0
- package/dist/transitions/placements.js +89 -0
- package/dist/transitions/preflightData.d.ts +21 -0
- package/dist/transitions/preflightData.js +317 -0
- package/dist/transitions/releaseHoldout.d.ts +8 -0
- package/dist/transitions/releaseHoldout.js +30 -0
- package/dist/transitions/resolveCaps.d.ts +28 -0
- package/dist/transitions/resolveCaps.js +68 -0
- package/dist/transitions/start.d.ts +26 -0
- package/dist/transitions/start.js +220 -0
- package/dist/transitions/stop.d.ts +8 -0
- package/dist/transitions/stop.js +25 -0
- package/dist/types/admin.d.ts +20 -0
- package/dist/types/admin.js +1 -0
- package/dist/types/assignments.d.ts +57 -0
- package/dist/types/assignments.js +1 -0
- package/dist/types/contracts.d.ts +141 -0
- package/dist/types/contracts.js +1 -0
- package/dist/types/endpoints.d.ts +135 -0
- package/dist/types/endpoints.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +8 -0
- package/dist/types/manifest.d.ts +42 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/puck.d.ts +23 -0
- package/dist/types/puck.js +1 -0
- package/dist/types/runtime.d.ts +59 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/snapshot.d.ts +117 -0
- package/dist/types/snapshot.js +1 -0
- package/package.json +138 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { EndpointContext, RefusalCode } from '../types/endpoints.js';
|
|
3
|
+
import type { DocId } from '../types/contracts.js';
|
|
4
|
+
import type { Snapshot } from '../types/snapshot.js';
|
|
5
|
+
export declare function resultsError(status: number, code: RefusalCode, message: string): Response;
|
|
6
|
+
export type SnapshotDocument = {
|
|
7
|
+
id: DocId;
|
|
8
|
+
experiment: DocId | {
|
|
9
|
+
id: DocId;
|
|
10
|
+
};
|
|
11
|
+
phase: number;
|
|
12
|
+
computedAt: string;
|
|
13
|
+
snapshot: Snapshot;
|
|
14
|
+
};
|
|
15
|
+
export declare function readSnapshots(req: PayloadRequest, ids: DocId[], { per, decisionId, seriesPhase }?: {
|
|
16
|
+
per?: 'phase' | 'experiment';
|
|
17
|
+
decisionId?: DocId | null;
|
|
18
|
+
seriesPhase?: number;
|
|
19
|
+
}): Promise<SnapshotDocument[]>;
|
|
20
|
+
export declare function handleResults<TUser extends {
|
|
21
|
+
id: string | number;
|
|
22
|
+
} = {
|
|
23
|
+
id: string | number;
|
|
24
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { resultsMetadata, newestSnapshotIds } from '../db/snapshots.js';
|
|
2
|
+
import { pooledExecutor } from '../db/executor.js';
|
|
3
|
+
const headers = {
|
|
4
|
+
'Cache-Control': 'no-store'
|
|
5
|
+
};
|
|
6
|
+
export function resultsError(status, code, message) {
|
|
7
|
+
return Response.json({
|
|
8
|
+
errors: [
|
|
9
|
+
{
|
|
10
|
+
code,
|
|
11
|
+
message
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}, {
|
|
15
|
+
status,
|
|
16
|
+
headers
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export async function readSnapshots(req, ids, { per = 'phase', decisionId, seriesPhase } = {}) {
|
|
20
|
+
const snapshotIds = seriesPhase === undefined ? await newestSnapshotIds(pooledExecutor(req.payload), ids, per) : [];
|
|
21
|
+
if (decisionId != null && !snapshotIds.some((id)=>String(id) === String(decisionId))) snapshotIds.push(decisionId);
|
|
22
|
+
if (seriesPhase === undefined && snapshotIds.length === 0) return [];
|
|
23
|
+
const { docs } = await req.payload.find({
|
|
24
|
+
collection: 'xp-results-snapshots',
|
|
25
|
+
req,
|
|
26
|
+
overrideAccess: false,
|
|
27
|
+
depth: 0,
|
|
28
|
+
pagination: false,
|
|
29
|
+
limit: seriesPhase === undefined ? snapshotIds.length : 0,
|
|
30
|
+
sort: '-computedAt',
|
|
31
|
+
where: seriesPhase === undefined ? {
|
|
32
|
+
id: {
|
|
33
|
+
in: snapshotIds
|
|
34
|
+
}
|
|
35
|
+
} : {
|
|
36
|
+
and: [
|
|
37
|
+
{
|
|
38
|
+
experiment: {
|
|
39
|
+
in: ids
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
phase: {
|
|
44
|
+
equals: seriesPhase
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return docs.sort((a, b)=>Date.parse(b.computedAt) - Date.parse(a.computedAt) || String(b.id).localeCompare(String(a.id), undefined, {
|
|
51
|
+
numeric: true
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
export async function handleResults(req, _ctx) {
|
|
55
|
+
const id = req.routeParams?.id;
|
|
56
|
+
if (typeof id !== 'string' && typeof id !== 'number') return resultsError(404, 'not-found', 'Experiment not found');
|
|
57
|
+
const params = new URL(req.url ?? 'http://localhost').searchParams;
|
|
58
|
+
const selected = params.get('phase');
|
|
59
|
+
if (selected !== null && (!/^\d+$/.test(selected) || !Number.isSafeInteger(Number(selected)) || Number(selected) < 1)) return resultsError(400, 'invalid-body', 'Choose a valid round');
|
|
60
|
+
const metadata = await resultsMetadata(pooledExecutor(req.payload), id);
|
|
61
|
+
if (!metadata) return resultsError(404, 'not-found', 'Experiment not found');
|
|
62
|
+
const { docs } = await req.payload.find({
|
|
63
|
+
collection: 'xp-phases',
|
|
64
|
+
req,
|
|
65
|
+
overrideAccess: false,
|
|
66
|
+
depth: 0,
|
|
67
|
+
pagination: false,
|
|
68
|
+
limit: 0,
|
|
69
|
+
where: {
|
|
70
|
+
experiment: {
|
|
71
|
+
equals: id
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
sort: 'phase'
|
|
75
|
+
});
|
|
76
|
+
const records = docs.sort((a, b)=>a.phase - b.phase);
|
|
77
|
+
const phases = records.map((record)=>({
|
|
78
|
+
phase: record.phase,
|
|
79
|
+
phaseKind: record.phaseKind,
|
|
80
|
+
startedAt: String(record.startedAt),
|
|
81
|
+
endedAt: record.endedAt == null ? null : String(record.endedAt),
|
|
82
|
+
arms: record.arms.map(({ key, label, kind })=>({
|
|
83
|
+
key,
|
|
84
|
+
label,
|
|
85
|
+
kind
|
|
86
|
+
})),
|
|
87
|
+
metrics: record.metricDefinitions.map(({ metricId, name, kind, role, resolvedCap, historyUnits, capNotice })=>({
|
|
88
|
+
metricId,
|
|
89
|
+
name,
|
|
90
|
+
kind,
|
|
91
|
+
role,
|
|
92
|
+
...resolvedCap === undefined ? {} : {
|
|
93
|
+
resolvedCap
|
|
94
|
+
},
|
|
95
|
+
...historyUnits === undefined ? {} : {
|
|
96
|
+
historyUnits
|
|
97
|
+
},
|
|
98
|
+
...capNotice ? {
|
|
99
|
+
capNotice
|
|
100
|
+
} : {}
|
|
101
|
+
}))
|
|
102
|
+
}));
|
|
103
|
+
const snapshots = await readSnapshots(req, [
|
|
104
|
+
id
|
|
105
|
+
], {
|
|
106
|
+
decisionId: metadata.snapshotId
|
|
107
|
+
});
|
|
108
|
+
const decision = metadata.snapshotId == null ? undefined : snapshots.find((snapshot)=>String(snapshot.id) === String(metadata.snapshotId))?.snapshot;
|
|
109
|
+
const phase = selected === null ? decision?.phase ?? (metadata.status === 'decided' ? records.filter((record)=>record.phaseKind === 'test').at(-1)?.phase : records.at(-1)?.phase) : Number(selected);
|
|
110
|
+
if (selected !== null && !records.some((record)=>record.phase === phase)) return resultsError(404, 'not-found', 'Round not found');
|
|
111
|
+
const snapshot = snapshots.find((snapshot)=>snapshot.phase === phase)?.snapshot ?? null;
|
|
112
|
+
const response = {
|
|
113
|
+
phases,
|
|
114
|
+
snapshot,
|
|
115
|
+
...decision ? {
|
|
116
|
+
decisionSnapshot: decision
|
|
117
|
+
} : {}
|
|
118
|
+
};
|
|
119
|
+
if (params.get('series') === '1') {
|
|
120
|
+
const series = phase === undefined ? [] : await readSnapshots(req, [
|
|
121
|
+
id
|
|
122
|
+
], {
|
|
123
|
+
seriesPhase: phase
|
|
124
|
+
});
|
|
125
|
+
response.series = series.reverse().flatMap(({ snapshot, computedAt })=>snapshot.metrics.flatMap((metric)=>metric.comparisons.map((comparison)=>({
|
|
126
|
+
computedAt,
|
|
127
|
+
metricId: metric.metricId,
|
|
128
|
+
arm: comparison.arm,
|
|
129
|
+
lift: comparison.lift,
|
|
130
|
+
ci: comparison.ciAdjusted
|
|
131
|
+
}))));
|
|
132
|
+
}
|
|
133
|
+
return Response.json(response, {
|
|
134
|
+
headers
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { EndpointContext } from '../types/endpoints.js';
|
|
3
|
+
export declare function handleSummary<TUser extends {
|
|
4
|
+
id: string | number;
|
|
5
|
+
} = {
|
|
6
|
+
id: string | number;
|
|
7
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readSnapshots, resultsError } from './results.js';
|
|
2
|
+
export async function handleSummary(req, _ctx) {
|
|
3
|
+
let ids;
|
|
4
|
+
try {
|
|
5
|
+
// Split before decoding: an opaque id can itself contain a comma.
|
|
6
|
+
const raw = new URL(req.url ?? 'http://localhost').search.slice(1).split('&').find((part)=>part.split('=')[0] === 'ids')?.slice(4);
|
|
7
|
+
if (raw === undefined) return resultsError(400, 'invalid-body', 'Provide experiment ids');
|
|
8
|
+
ids = raw.split(',').map((id)=>decodeURIComponent(id.replaceAll('+', ' ')));
|
|
9
|
+
} catch {
|
|
10
|
+
return resultsError(400, 'invalid-body', 'Could not parse experiment ids');
|
|
11
|
+
}
|
|
12
|
+
if (ids.length > 100) return resultsError(422, 'too-many-ids', 'Request at most 100 experiments');
|
|
13
|
+
if (ids.some((id)=>!id.trim())) return resultsError(422, 'invalid-ids', 'Experiment ids must be nonempty');
|
|
14
|
+
const snapshots = await readSnapshots(req, [
|
|
15
|
+
...new Set(ids)
|
|
16
|
+
], {
|
|
17
|
+
per: 'experiment'
|
|
18
|
+
});
|
|
19
|
+
const response = Object.create(null);
|
|
20
|
+
for (const id of ids){
|
|
21
|
+
const snapshot = snapshots.find((row)=>String(typeof row.experiment === 'object' ? row.experiment.id : row.experiment) === id)?.snapshot;
|
|
22
|
+
response[id] = snapshot ? {
|
|
23
|
+
exposures: snapshot.exposures.total,
|
|
24
|
+
days: Math.max(0, (Date.parse(snapshot.window.to) - Date.parse(snapshot.window.from)) / 86_400_000),
|
|
25
|
+
verdict: {
|
|
26
|
+
code: snapshot.verdict.code,
|
|
27
|
+
headline: snapshot.verdict.headline
|
|
28
|
+
}
|
|
29
|
+
} : {
|
|
30
|
+
exposures: 0,
|
|
31
|
+
days: 0,
|
|
32
|
+
verdict: null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return Response.json(response, {
|
|
36
|
+
headers: {
|
|
37
|
+
'Cache-Control': 'no-store'
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { EndpointContext } from '../types/endpoints.js';
|
|
3
|
+
export declare function handleStart<TUser extends {
|
|
4
|
+
id: string | number;
|
|
5
|
+
} = {
|
|
6
|
+
id: string | number;
|
|
7
|
+
}>(req: PayloadRequest, ctx: EndpointContext<TUser>): Promise<Response>;
|
|
8
|
+
export declare function handleAllocate<TUser extends {
|
|
9
|
+
id: string | number;
|
|
10
|
+
} = {
|
|
11
|
+
id: string | number;
|
|
12
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
13
|
+
export declare function handleAddMetric<TUser extends {
|
|
14
|
+
id: string | number;
|
|
15
|
+
} = {
|
|
16
|
+
id: string | number;
|
|
17
|
+
}>(req: PayloadRequest, ctx: EndpointContext<TUser>): Promise<Response>;
|
|
18
|
+
export declare function handleStop<TUser extends {
|
|
19
|
+
id: string | number;
|
|
20
|
+
} = {
|
|
21
|
+
id: string | number;
|
|
22
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
23
|
+
export declare function handleDecide<TUser extends {
|
|
24
|
+
id: string | number;
|
|
25
|
+
} = {
|
|
26
|
+
id: string | number;
|
|
27
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
28
|
+
export declare function handleReleaseHoldout<TUser extends {
|
|
29
|
+
id: string | number;
|
|
30
|
+
} = {
|
|
31
|
+
id: string | number;
|
|
32
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
33
|
+
export declare function handleMakeContent<TUser extends {
|
|
34
|
+
id: string | number;
|
|
35
|
+
} = {
|
|
36
|
+
id: string | number;
|
|
37
|
+
}>(req: PayloadRequest, ctx: EndpointContext<TUser>): Promise<Response>;
|
|
38
|
+
export declare function handleArchive<TUser extends {
|
|
39
|
+
id: string | number;
|
|
40
|
+
} = {
|
|
41
|
+
id: string | number;
|
|
42
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { XpRefusal } from '../errors.js';
|
|
2
|
+
import { start } from '../transitions/start.js';
|
|
3
|
+
import { allocate } from '../transitions/allocate.js';
|
|
4
|
+
import { addMetric } from '../transitions/addMetric.js';
|
|
5
|
+
import { stop } from '../transitions/stop.js';
|
|
6
|
+
import { decide } from '../transitions/decide.js';
|
|
7
|
+
import { releaseHoldout } from '../transitions/releaseHoldout.js';
|
|
8
|
+
import { makeContent } from '../transitions/makeContent.js';
|
|
9
|
+
import { archive } from '../transitions/archive.js';
|
|
10
|
+
const invalidBody = ()=>Response.json({
|
|
11
|
+
errors: [
|
|
12
|
+
{
|
|
13
|
+
code: 'invalid-body',
|
|
14
|
+
message: 'Enter a valid request body.'
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}, {
|
|
18
|
+
status: 400
|
|
19
|
+
});
|
|
20
|
+
async function body(req) {
|
|
21
|
+
try {
|
|
22
|
+
if (!req.body && !req.data) return {};
|
|
23
|
+
// An empty body (Content-Length: 0) means no options, not a malformed request.
|
|
24
|
+
const text = req.data === undefined ? await req.text() : '';
|
|
25
|
+
const value = req.data ?? (text.trim() ? JSON.parse(text) : {});
|
|
26
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const only = (value, keys)=>Object.keys(value).every((key)=>keys.includes(key));
|
|
32
|
+
async function respond(req, action) {
|
|
33
|
+
const id = req.routeParams?.id;
|
|
34
|
+
if (typeof id !== 'string' && typeof id !== 'number') return Response.json({
|
|
35
|
+
errors: [
|
|
36
|
+
{
|
|
37
|
+
code: 'not-found',
|
|
38
|
+
message: 'Experiment not found'
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}, {
|
|
42
|
+
status: 404
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
return Response.json(await action(id), {
|
|
46
|
+
headers: {
|
|
47
|
+
'Cache-Control': 'no-store'
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
if (cause instanceof XpRefusal) return Response.json({
|
|
52
|
+
errors: [
|
|
53
|
+
{
|
|
54
|
+
code: cause.code,
|
|
55
|
+
message: cause.message,
|
|
56
|
+
...cause.detail === undefined ? {} : {
|
|
57
|
+
detail: cause.detail
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}, {
|
|
62
|
+
status: cause.status === 409 ? 422 : cause.status
|
|
63
|
+
});
|
|
64
|
+
throw cause;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function handleStart(req, ctx) {
|
|
68
|
+
const value = await body(req);
|
|
69
|
+
if (!value || !only(value, [])) return invalidBody();
|
|
70
|
+
return respond(req, (id)=>start(req, {
|
|
71
|
+
experimentId: id,
|
|
72
|
+
options: ctx.options
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
export async function handleAllocate(req, _ctx) {
|
|
76
|
+
const value = await body(req);
|
|
77
|
+
if (!value || !only(value, [
|
|
78
|
+
'allocation'
|
|
79
|
+
]) || typeof value.allocation !== 'number' || !Number.isInteger(value.allocation) || value.allocation < 1 || value.allocation > 100) return invalidBody();
|
|
80
|
+
const allocation = value.allocation;
|
|
81
|
+
return respond(req, (id)=>allocate(req, {
|
|
82
|
+
experimentId: id,
|
|
83
|
+
allocation
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
export async function handleAddMetric(req, ctx) {
|
|
87
|
+
const value = await body(req);
|
|
88
|
+
if (!value || !only(value, [
|
|
89
|
+
'metric',
|
|
90
|
+
'role'
|
|
91
|
+
]) || !(typeof value.metric === 'string' && value.metric.trim() || typeof value.metric === 'number' && Number.isSafeInteger(value.metric)) || value.role !== 'secondary' && value.role !== 'guardrail') return invalidBody();
|
|
92
|
+
const metric = value.metric, role = value.role;
|
|
93
|
+
return respond(req, (id)=>addMetric(req, {
|
|
94
|
+
experimentId: id,
|
|
95
|
+
metric,
|
|
96
|
+
role,
|
|
97
|
+
options: ctx.options
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
export async function handleStop(req, _ctx) {
|
|
101
|
+
const value = await body(req);
|
|
102
|
+
if (!value || !only(value, [])) return invalidBody();
|
|
103
|
+
return respond(req, (id)=>stop(req, {
|
|
104
|
+
experimentId: id
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
export async function handleDecide(req, _ctx) {
|
|
108
|
+
const value = await body(req);
|
|
109
|
+
if (!value || !only(value, [
|
|
110
|
+
'winnerArm',
|
|
111
|
+
'note',
|
|
112
|
+
'confirmation'
|
|
113
|
+
]) || typeof value.winnerArm !== 'string' || value.note !== undefined && typeof value.note !== 'string' || value.confirmation !== undefined && typeof value.confirmation !== 'string') return invalidBody();
|
|
114
|
+
const { winnerArm, note, confirmation } = value;
|
|
115
|
+
return respond(req, (id)=>decide(req, {
|
|
116
|
+
experimentId: id,
|
|
117
|
+
winnerArm,
|
|
118
|
+
note,
|
|
119
|
+
confirmation
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
export async function handleReleaseHoldout(req, _ctx) {
|
|
123
|
+
const value = await body(req);
|
|
124
|
+
if (!value || !only(value, [])) return invalidBody();
|
|
125
|
+
return respond(req, (id)=>releaseHoldout(req, {
|
|
126
|
+
experimentId: id
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
export async function handleMakeContent(req, ctx) {
|
|
130
|
+
const value = await body(req);
|
|
131
|
+
if (!value || !only(value, [])) return invalidBody();
|
|
132
|
+
return respond(req, (id)=>makeContent(req, {
|
|
133
|
+
experimentId: id,
|
|
134
|
+
options: ctx.options
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
export async function handleArchive(req, _ctx) {
|
|
138
|
+
const value = await body(req);
|
|
139
|
+
if (!value || !only(value, [
|
|
140
|
+
'mode',
|
|
141
|
+
'confirmation'
|
|
142
|
+
]) || ![
|
|
143
|
+
'stopped',
|
|
144
|
+
'after-content',
|
|
145
|
+
'discard',
|
|
146
|
+
'hard-coded'
|
|
147
|
+
].includes(value.mode) || value.confirmation !== undefined && typeof value.confirmation !== 'string') return invalidBody();
|
|
148
|
+
const { mode, confirmation } = value;
|
|
149
|
+
return respond(req, (id)=>archive(req, {
|
|
150
|
+
experimentId: id,
|
|
151
|
+
mode,
|
|
152
|
+
confirmation
|
|
153
|
+
}));
|
|
154
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RefusalCode } from './types/endpoints.js';
|
|
2
|
+
import type { Status } from './types/contracts.js';
|
|
3
|
+
export declare class XpNotImplemented extends Error {
|
|
4
|
+
readonly lane: string;
|
|
5
|
+
name: string;
|
|
6
|
+
constructor(lane: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class XpLockedFieldError extends Error {
|
|
9
|
+
readonly field: string;
|
|
10
|
+
readonly experimentStatus: Status;
|
|
11
|
+
name: string;
|
|
12
|
+
readonly status = 400;
|
|
13
|
+
readonly isPublic = true;
|
|
14
|
+
constructor(field: string, experimentStatus: Status);
|
|
15
|
+
}
|
|
16
|
+
export declare class XpNoTransactionError extends Error {
|
|
17
|
+
name: string;
|
|
18
|
+
constructor();
|
|
19
|
+
}
|
|
20
|
+
export declare class XpRefusal extends Error {
|
|
21
|
+
readonly code: RefusalCode;
|
|
22
|
+
readonly detail: unknown | undefined;
|
|
23
|
+
readonly status: 409 | 422 | 500 | 404;
|
|
24
|
+
name: string;
|
|
25
|
+
readonly isPublic = true;
|
|
26
|
+
constructor(code: RefusalCode, message: string, detail: unknown | undefined, status: 409 | 422 | 500 | 404);
|
|
27
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class XpNotImplemented extends Error {
|
|
2
|
+
lane;
|
|
3
|
+
name = 'XpNotImplemented';
|
|
4
|
+
constructor(lane){
|
|
5
|
+
super(lane), this.lane = lane;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class XpLockedFieldError extends Error {
|
|
9
|
+
field;
|
|
10
|
+
experimentStatus;
|
|
11
|
+
name = 'XpLockedFieldError';
|
|
12
|
+
status = 400;
|
|
13
|
+
isPublic = true;
|
|
14
|
+
constructor(field, experimentStatus){
|
|
15
|
+
super(`${field} is locked in ${experimentStatus}`), this.field = field, this.experimentStatus = experimentStatus;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class XpNoTransactionError extends Error {
|
|
19
|
+
name = 'XpNoTransactionError';
|
|
20
|
+
constructor(){
|
|
21
|
+
super('An active transaction is required');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class XpRefusal extends Error {
|
|
25
|
+
code;
|
|
26
|
+
detail;
|
|
27
|
+
status;
|
|
28
|
+
name = 'XpRefusal';
|
|
29
|
+
isPublic = true;
|
|
30
|
+
constructor(code, message, detail, status){
|
|
31
|
+
super(message), this.code = code, this.detail = detail, this.status = status;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class EventsBodyError extends Error {
|
|
2
|
+
readonly status: 413 | 422;
|
|
3
|
+
readonly code: 'body-too-large' | 'invalid-body';
|
|
4
|
+
constructor(status: 413 | 422, code: 'body-too-large' | 'invalid-body', message: string);
|
|
5
|
+
}
|
|
6
|
+
/** Bound retained bytes while reading the original Fetch stream, before parsing. */
|
|
7
|
+
export declare function readEvents(body: ReadableStream<Uint8Array> | null | undefined, maxBytes: number): Promise<unknown[]>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MAX_EVENTS_PER_BODY } from '../constants.js';
|
|
2
|
+
export class EventsBodyError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code, message){
|
|
6
|
+
super(message), this.status = status, this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Bound retained bytes while reading the original Fetch stream, before parsing. */ export async function readEvents(body, maxBytes) {
|
|
10
|
+
if (!body) throw new EventsBodyError(422, 'invalid-body', 'Expected an events array');
|
|
11
|
+
const reader = body.getReader();
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let size = 0;
|
|
14
|
+
try {
|
|
15
|
+
while(true){
|
|
16
|
+
const { done, value } = await reader.read();
|
|
17
|
+
if (done) break;
|
|
18
|
+
size += value.byteLength;
|
|
19
|
+
if (size > maxBytes) {
|
|
20
|
+
// Cancellation must not wait for an uncooperative sender to finish.
|
|
21
|
+
void reader.cancel().catch(()=>{});
|
|
22
|
+
throw new EventsBodyError(413, 'body-too-large', 'Events body is too large');
|
|
23
|
+
}
|
|
24
|
+
chunks.push(value);
|
|
25
|
+
}
|
|
26
|
+
const bytes = new Uint8Array(size);
|
|
27
|
+
let offset = 0;
|
|
28
|
+
for (const chunk of chunks){
|
|
29
|
+
bytes.set(chunk, offset);
|
|
30
|
+
offset += chunk.byteLength;
|
|
31
|
+
}
|
|
32
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
33
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !('events' in parsed) || !Array.isArray(parsed.events) || parsed.events.length > MAX_EVENTS_PER_BODY) {
|
|
34
|
+
throw new Error('Expected at most 20 events');
|
|
35
|
+
}
|
|
36
|
+
return parsed.events;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof EventsBodyError) throw error;
|
|
39
|
+
throw new EventsBodyError(422, 'invalid-body', 'Invalid events body');
|
|
40
|
+
} finally{
|
|
41
|
+
reader.releaseLock();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Startup validation; hosts may supply options from JavaScript or config files. */
|
|
2
|
+
export declare function validateDimensions(names: unknown): void;
|
|
3
|
+
/** Pick only server-computed request attributes; never pass browser event data. */
|
|
4
|
+
export declare function exposureDimensions(attributes: Readonly<Record<string, unknown>>, names?: readonly string[]): Record<string, string | number | boolean> | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const identifyingKeys = new Set([
|
|
2
|
+
'userid',
|
|
3
|
+
'visitorid',
|
|
4
|
+
'unitid',
|
|
5
|
+
'id',
|
|
6
|
+
'email',
|
|
7
|
+
'emailaddress',
|
|
8
|
+
'sessionid',
|
|
9
|
+
'session',
|
|
10
|
+
'ip',
|
|
11
|
+
'ipaddress',
|
|
12
|
+
'phone',
|
|
13
|
+
'name'
|
|
14
|
+
]);
|
|
15
|
+
/** Startup validation; hosts may supply options from JavaScript or config files. */ export function validateDimensions(names) {
|
|
16
|
+
if (names === undefined) return;
|
|
17
|
+
if (!Array.isArray(names) || names.length > 8) {
|
|
18
|
+
throw new Error('Experiments events.dimensions must be an array of at most 8 unique attribute names');
|
|
19
|
+
}
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const name of names){
|
|
22
|
+
if (typeof name === 'string' && identifyingKeys.has(name.toLowerCase().replace(/[_-]/g, ''))) {
|
|
23
|
+
throw new Error(`Experiments events.dimensions forbids identifying key: ${name}`);
|
|
24
|
+
}
|
|
25
|
+
if (typeof name !== 'string' || !/^[a-zA-Z][a-zA-Z0-9_]{0,39}$/.test(name)) {
|
|
26
|
+
throw new Error('Experiments events.dimensions names must match ^[a-zA-Z][a-zA-Z0-9_]{0,39}$');
|
|
27
|
+
}
|
|
28
|
+
if (seen.has(name)) throw new Error(`Experiments events.dimensions names must be unique: ${name}`);
|
|
29
|
+
seen.add(name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Pick only server-computed request attributes; never pass browser event data. */ export function exposureDimensions(attributes, names = []) {
|
|
33
|
+
const entries = [];
|
|
34
|
+
for (const name of names){
|
|
35
|
+
if (!Object.hasOwn(attributes, name)) continue;
|
|
36
|
+
const value = attributes[name];
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
const truncated = Array.from(value).slice(0, 64).join('');
|
|
39
|
+
// JSONB rejects NUL and unpaired UTF-16 surrogates. Optional telemetry
|
|
40
|
+
// must never make an otherwise valid exposure fail to persist.
|
|
41
|
+
if (!/\u0000|[\uD800-\uDFFF]/u.test(truncated)) entries.push([
|
|
42
|
+
name,
|
|
43
|
+
truncated
|
|
44
|
+
]);
|
|
45
|
+
} else if (typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) entries.push([
|
|
46
|
+
name,
|
|
47
|
+
value
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
return entries.length ? Object.fromEntries(entries) : null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { XP_PREVIEW_PARAM } from '../constants.js';
|
|
2
|
+
/** These signals only suppress telemetry; they never authorize a preview or an assignment. */ export function isPreview(request) {
|
|
3
|
+
if (new URL(request.url).searchParams.has(XP_PREVIEW_PARAM)) return true;
|
|
4
|
+
const referrer = request.headers.get('referer');
|
|
5
|
+
try {
|
|
6
|
+
if (referrer && new URL(referrer).searchParams.has(XP_PREVIEW_PARAM)) return true;
|
|
7
|
+
} catch {}
|
|
8
|
+
return (request.headers.get('cookie') ?? '').split(';').some((part)=>part.trim().startsWith('__prerender_bypass='));
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server.js';
|
|
2
|
+
/** Browser beacons carry their page URL in Referer. It supplies targeting context only. */ export function sourceRequest(request) {
|
|
3
|
+
const referrer = request.headers.get('referer');
|
|
4
|
+
if (!referrer) return null;
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(referrer);
|
|
7
|
+
if (url.origin !== request.nextUrl.origin) return null;
|
|
8
|
+
return new NextRequest(url, {
|
|
9
|
+
headers: request.headers
|
|
10
|
+
});
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EventRow } from '../db/events.js';
|
|
2
|
+
import type { AssignmentResult } from '../runtime/assign.js';
|
|
3
|
+
import type { ResolvedOptions } from '../types/contracts.js';
|
|
4
|
+
import type { Manifest } from '../types/manifest.js';
|
|
5
|
+
export declare function eventRows<TUser extends {
|
|
6
|
+
id: string | number;
|
|
7
|
+
}>(events: unknown[], manifest: Manifest, assignments: AssignmentResult, options: ResolvedOptions<TUser>, visitorId: string, userId: string | undefined, ts: string, requestAttributes?: Readonly<Record<string, unknown>>): EventRow[];
|