@preprio/toolkit 0.6.0-alpha.1 → 0.6.0-alpha.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.
Files changed (42) hide show
  1. package/README.md +46 -30
  2. package/dist/astro/{chunk-O7N6GPAH.js → chunk-CUDYSNJD.js} +1 -1
  3. package/dist/astro/graphql.js +1 -1
  4. package/dist/astro/index.cjs +1 -1
  5. package/dist/astro/index.js +1 -1
  6. package/dist/{chunk-VI6QZTBM.js → chunk-3YZANSVY.js} +1 -1
  7. package/dist/{chunk-TBKSBXGH.js → chunk-FC53KN4H.js} +1 -1
  8. package/dist/{chunk-3JLWZQ37.js → chunk-UH2OK4XC.js} +1 -1
  9. package/dist/{chunk-YWIKKWBR.js → chunk-VY5D3IED.js} +1 -1
  10. package/dist/graphql/codegen.js +3 -3
  11. package/dist/graphql.js +3 -3
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/nextjs/chunk-5XGJJVGX.js +544 -0
  17. package/dist/nextjs/components.cjs +166 -9
  18. package/dist/nextjs/components.d.cts +31 -3
  19. package/dist/nextjs/components.d.ts +31 -3
  20. package/dist/nextjs/components.js +166 -10
  21. package/dist/nextjs/graphql.cjs +588 -50
  22. package/dist/nextjs/graphql.d.cts +95 -28
  23. package/dist/nextjs/graphql.d.ts +95 -28
  24. package/dist/nextjs/graphql.js +287 -50
  25. package/dist/nextjs/index.cjs +3 -1
  26. package/dist/nextjs/index.d.cts +32 -3
  27. package/dist/nextjs/index.d.ts +32 -3
  28. package/dist/nextjs/index.js +8 -313
  29. package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.cts} +1 -1
  30. package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.ts} +1 -1
  31. package/dist/nuxt/{chunk-IQDSBPDN.js → chunk-KROR7RXW.js} +1 -1
  32. package/dist/nuxt/graphql.js +1 -1
  33. package/dist/nuxt/index.cjs +1 -1
  34. package/dist/nuxt/index.js +1 -1
  35. package/dist/react/components.cjs +1 -1
  36. package/dist/react/components.js +1 -1
  37. package/dist/sveltekit/{chunk-OKYZ6HPH.js → chunk-INDMNPEM.js} +1 -1
  38. package/dist/sveltekit/graphql.js +1 -1
  39. package/dist/sveltekit/index.cjs +1 -1
  40. package/dist/sveltekit/index.js +1 -1
  41. package/package.json +1 -1
  42. package/dist/nextjs/chunk-BQ6IGSH6.js +0 -231
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  PreprError,
3
+ createPreprMiddleware,
3
4
  getPreprHeaders,
4
5
  getPreprHeadersFromHeaders,
5
6
  isPersonalizedResponse,
7
+ preprDraftRedirect,
8
+ resolvePreprBucket,
6
9
  validatePreprToken
7
- } from "./chunk-BQ6IGSH6.js";
10
+ } from "./chunk-5XGJJVGX.js";
8
11
 
9
12
  // src/core/variants/constants.ts
10
13
  var PREPR_CONTEXT_ALIAS = "_preprContext";
@@ -871,6 +874,49 @@ async function loadPreprSchema(url, options = {}) {
871
874
  return schema;
872
875
  }
873
876
 
877
+ // src/core/refresh.ts
878
+ var PREPR_REFRESH_COOKIE = "prepr_refresh";
879
+ var PREPR_REFRESH_HEADER = "Prepr-Refresh";
880
+ var REFRESH_MAX_PATHS = 5;
881
+ var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
882
+ function safePath(path) {
883
+ return SAFE_PATH.test(path) ? path : void 0;
884
+ }
885
+ function isFresh(at, freshFor, now = Date.now()) {
886
+ const age = now - at;
887
+ return at > 0 && age >= 0 && age < freshFor;
888
+ }
889
+ function cleanEntries(entries) {
890
+ const out = [];
891
+ for (const entry of entries) {
892
+ if (out.length === REFRESH_MAX_PATHS) break;
893
+ if (safePath(entry.path) && !out.some((e) => e.path === entry.path)) {
894
+ out.push(entry);
895
+ }
896
+ }
897
+ return out;
898
+ }
899
+ function parseRefresh(raw) {
900
+ return cleanEntries(
901
+ (raw ?? "").split("|").map((pair) => {
902
+ const split = pair.lastIndexOf("~");
903
+ const at = Number(split < 0 ? "" : pair.slice(split + 1));
904
+ return {
905
+ path: split < 0 ? pair : pair.slice(0, split),
906
+ at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
907
+ };
908
+ })
909
+ );
910
+ }
911
+ function serializeRefresh(entries) {
912
+ return cleanEntries(entries).map((entry) => `${entry.path}~${Math.floor(entry.at)}`).join("|");
913
+ }
914
+ function parseRefreshPaths(header) {
915
+ return cleanEntries(
916
+ (header ?? "").split("|").map((path) => ({ path, at: 0 }))
917
+ ).map((entry) => entry.path);
918
+ }
919
+
874
920
  // src/nextjs/decisions.ts
875
921
  var PREPR_DECISIONS_COOKIE = "prepr_decisions";
876
922
  var DECISIONS_MAX_GROUPS = 40;
@@ -902,61 +948,230 @@ function forwardedHeaders(request, response) {
902
948
  }
903
949
  return getPreprHeadersFromHeaders(headers);
904
950
  }
951
+ var preprClientInternals = /* @__PURE__ */ new WeakMap();
952
+ var DEFAULT_FRESH_FOR_MS = 1e4;
953
+ async function decidePage(client, seenGroups, headers, document2, variables, timeoutMs = 1e3) {
954
+ if (!await client.hasSegments(document2, variables)) return void 0;
955
+ const decided = await client.decide(document2, variables, {
956
+ topLevelOnly: true,
957
+ headers,
958
+ init: { signal: AbortSignal.timeout(timeoutMs) }
959
+ });
960
+ let byVariables = seenGroups.get(document2);
961
+ if (!byVariables) {
962
+ byVariables = /* @__PURE__ */ new Map();
963
+ seenGroups.set(document2, byVariables);
964
+ }
965
+ const pageKey = JSON.stringify(variables ?? null);
966
+ let groups = byVariables.get(pageKey);
967
+ if (!groups) {
968
+ if (byVariables.size >= SEEN_MAX_PAGES) {
969
+ byVariables.delete(byVariables.keys().next().value);
970
+ }
971
+ groups = /* @__PURE__ */ new Set();
972
+ byVariables.set(pageKey, groups);
973
+ }
974
+ for (const group of decided.keys()) groups.add(group);
975
+ return decided;
976
+ }
977
+ function writeDecisions(request, response, decided, paths) {
978
+ const next = /* @__PURE__ */ new Map();
979
+ for (const decisions of decided) {
980
+ for (const [group, variants] of decisions) {
981
+ if (SAFE_ID.test(group) && !next.has(group)) {
982
+ next.set(
983
+ group,
984
+ variants.filter((v) => SAFE_ID.test(v))
985
+ );
986
+ }
987
+ }
988
+ }
989
+ for (const [group, variants] of parseDecisions(
990
+ request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
991
+ )) {
992
+ if (!next.has(group)) next.set(group, variants);
993
+ }
994
+ const options = {
995
+ path: "/",
996
+ sameSite: "lax",
997
+ httpOnly: false,
998
+ secure: request.nextUrl.protocol === "https:"
999
+ };
1000
+ if (next.size > 0) {
1001
+ response.cookies.set(
1002
+ PREPR_DECISIONS_COOKIE,
1003
+ serializeDecisions(next),
1004
+ options
1005
+ );
1006
+ }
1007
+ if (paths.length > 0) {
1008
+ const at = Date.now();
1009
+ response.cookies.set(
1010
+ PREPR_REFRESH_COOKIE,
1011
+ serializeRefresh([
1012
+ ...paths.map((path) => ({ path, at })),
1013
+ ...parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value)
1014
+ ]),
1015
+ options
1016
+ );
1017
+ }
1018
+ }
1019
+ function pagePath(request) {
1020
+ return request.nextUrl.basePath + request.nextUrl.pathname;
1021
+ }
1022
+ function contextChanged(request, headers) {
1023
+ return headers.get("Prepr-Customer-Id-Created") === "true" || headers.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
1024
+ (key) => key.startsWith("utm_")
1025
+ );
1026
+ }
905
1027
  async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
906
1028
  try {
907
1029
  if (options?.onlyIf && !await options.onlyIf()) return;
908
- if (!await client.hasSegments(document2, variables)) return;
1030
+ const known = seenGroups.get(document2)?.get(JSON.stringify(variables ?? null));
1031
+ const path = pagePath(request);
909
1032
  const headers = mergePreprHeaders(forwardedHeaders(request, response));
910
1033
  const stored = parseDecisions(
911
1034
  request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
912
1035
  );
913
- const pageKey = `${JSON.stringify(variables ?? null)}`;
914
- let byVariables = seenGroups.get(document2);
915
- if (!byVariables) {
916
- byVariables = /* @__PURE__ */ new Map();
917
- seenGroups.set(document2, byVariables);
918
- }
919
- const known = byVariables.get(pageKey);
920
- const isNewVisitor = headers.get("Prepr-Customer-Id-Created") === "true";
921
- const contextChanged = isNewVisitor || headers.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
922
- (key) => key.startsWith("utm_")
923
- );
924
- if (options?.reuse !== false && !contextChanged && known && [...known].every((group) => stored.has(group))) {
1036
+ const decidedAt = parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value).find(
1037
+ (entry) => entry.path === path
1038
+ )?.at ?? 0;
1039
+ const fresh = isFresh(decidedAt, options?.freshFor ?? DEFAULT_FRESH_FOR_MS);
1040
+ if (fresh && known && !contextChanged(request, headers) && [...known].every((group) => stored.has(group))) {
925
1041
  return;
926
1042
  }
927
- const decided = await client.decide(document2, variables, {
928
- topLevelOnly: true,
1043
+ const decided = await decidePage(
1044
+ client,
1045
+ seenGroups,
929
1046
  headers,
930
- init: { signal: AbortSignal.timeout(options?.timeoutMs ?? 1e3) }
1047
+ document2,
1048
+ variables,
1049
+ options?.timeoutMs
1050
+ );
1051
+ if (!decided || decided.size === 0) return;
1052
+ writeDecisions(request, response, [decided], [path]);
1053
+ } catch (error) {
1054
+ if (isFrameworkSignal(error)) throw error;
1055
+ }
1056
+ }
1057
+
1058
+ // src/nextjs/proxy.ts
1059
+ import { NextResponse } from "next/server";
1060
+ function isPagePath(pathname) {
1061
+ return !pathname.startsWith("/_next/") && !pathname.startsWith("/api/") && !/\.[^/]*$/.test(pathname);
1062
+ }
1063
+ function createPreprProxy(options) {
1064
+ const internals = preprClientInternals.get(options.client);
1065
+ if (!internals) {
1066
+ throw new PreprError(
1067
+ "createPreprProxy needs a client made by createPreprClient from @preprio/toolkit/nextjs/graphql",
1068
+ "CONTEXT_ERROR",
1069
+ "createPreprProxy"
1070
+ );
1071
+ }
1072
+ const { client, seenGroups, url } = internals;
1073
+ const preview = options.preview ?? false;
1074
+ const allowed = async (request) => {
1075
+ if (!options.onlyIf) return true;
1076
+ try {
1077
+ return await options.onlyIf(request);
1078
+ } catch {
1079
+ return false;
1080
+ }
1081
+ };
1082
+ const lookup = (target) => {
1083
+ try {
1084
+ return options.page(target) ?? null;
1085
+ } catch {
1086
+ return null;
1087
+ }
1088
+ };
1089
+ async function refresh(request, response, paths) {
1090
+ const answer = new NextResponse(null, {
1091
+ status: 204,
1092
+ headers: { "Cache-Control": "no-store" }
931
1093
  });
932
- if (!known && byVariables.size >= SEEN_MAX_PAGES) {
933
- byVariables.delete(byVariables.keys().next().value);
1094
+ for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
1095
+ const site = request.headers.get("sec-fetch-site");
1096
+ if (request.method !== "GET" || site !== null && site !== "same-origin" || !await allowed(request)) {
1097
+ return answer;
934
1098
  }
935
- const groups = known ?? /* @__PURE__ */ new Set();
936
- for (const group of decided.keys()) groups.add(group);
937
- byVariables.set(pageKey, groups);
938
- const next = /* @__PURE__ */ new Map();
939
- for (const [group, variants] of decided) {
940
- if (SAFE_ID.test(group)) {
941
- next.set(
942
- group,
943
- variants.filter((v) => SAFE_ID.test(v))
944
- );
945
- }
1099
+ const own = new Set(
1100
+ parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value).map(
1101
+ (entry) => entry.path
1102
+ )
1103
+ );
1104
+ const basePath = request.nextUrl.basePath;
1105
+ const headers = mergePreprHeaders(forwardedHeaders(request, response));
1106
+ const decided = await Promise.all(
1107
+ paths.map(async (path) => {
1108
+ if (!own.has(path) || !path.startsWith(basePath)) return void 0;
1109
+ const pathname = path.slice(basePath.length) || "/";
1110
+ if (!isPagePath(pathname)) return void 0;
1111
+ const target = lookup(new URL(pathname, request.url));
1112
+ if (!target) return void 0;
1113
+ try {
1114
+ const decisions = await decidePage(
1115
+ client,
1116
+ seenGroups,
1117
+ headers,
1118
+ target.document,
1119
+ target.variables,
1120
+ options.timeoutMs
1121
+ );
1122
+ return decisions?.size ? [path, decisions] : void 0;
1123
+ } catch (error) {
1124
+ if (isFrameworkSignal(error)) throw error;
1125
+ return void 0;
1126
+ }
1127
+ })
1128
+ );
1129
+ const found = decided.filter((entry) => entry !== void 0);
1130
+ writeDecisions(
1131
+ request,
1132
+ answer,
1133
+ found.map(([, decisions]) => decisions),
1134
+ found.map(([path]) => path)
1135
+ );
1136
+ return answer;
1137
+ }
1138
+ return async (request) => {
1139
+ if (request.headers.has(PREPR_REFRESH_HEADER)) {
1140
+ return refresh(
1141
+ request,
1142
+ createPreprMiddleware(request, { preview, features: options.features }),
1143
+ parseRefreshPaths(request.headers.get(PREPR_REFRESH_HEADER))
1144
+ );
946
1145
  }
947
- for (const [group, variants] of stored) {
948
- if (!next.has(group)) next.set(group, variants);
1146
+ const target = isPagePath(request.nextUrl.pathname) ? lookup(request.nextUrl) : null;
1147
+ if (target && preview) {
1148
+ const draft = preprDraftRedirect(request, { preview });
1149
+ if (draft) return draft;
949
1150
  }
950
- if (next.size === 0) return;
951
- response.cookies.set(PREPR_DECISIONS_COOKIE, serializeDecisions(next), {
952
- path: "/",
953
- sameSite: "lax",
954
- // Read by the inline switch script before first paint.
955
- httpOnly: false
1151
+ const response = createPreprMiddleware(request, {
1152
+ preview,
1153
+ features: options.features
956
1154
  });
957
- } catch (error) {
958
- if (isFrameworkSignal(error)) throw error;
959
- }
1155
+ if (target && await allowed(request)) {
1156
+ await Promise.all([
1157
+ resolvePreprBucket(request, response, {
1158
+ url,
1159
+ preview,
1160
+ onlyIf: () => options.client.hasAbTests(target.document, target.variables)
1161
+ }),
1162
+ resolveDecisionsWith(
1163
+ client,
1164
+ seenGroups,
1165
+ request,
1166
+ response,
1167
+ target.document,
1168
+ target.variables,
1169
+ { freshFor: options.freshFor, timeoutMs: options.timeoutMs }
1170
+ )
1171
+ ]);
1172
+ }
1173
+ return response;
1174
+ };
960
1175
  }
961
1176
 
962
1177
  // src/nextjs/stack.ts
@@ -989,7 +1204,11 @@ function applyPreprAbBucket() {
989
1204
  state.__preprAbSheet.textContent = css;
990
1205
  }
991
1206
  }
992
- function applyPreprDecisions() {
1207
+ function applyPreprDecisions(view) {
1208
+ const viewState = window;
1209
+ const current = view === void 0 ? location.pathname + location.search : view;
1210
+ if (viewState.__preprDecisionsView === current) return;
1211
+ viewState.__preprDecisionsView = current;
993
1212
  const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
994
1213
  let raw;
995
1214
  try {
@@ -1031,7 +1250,11 @@ function applyPreprDecisions() {
1031
1250
  }
1032
1251
 
1033
1252
  // src/nextjs/stack.ts
1034
- import { PreprAbSwitch, PreprVariantTracking } from "./components.js";
1253
+ import {
1254
+ PreprAbSwitch,
1255
+ PreprPersonalizationSwitch,
1256
+ PreprVariantTracking
1257
+ } from "./components.js";
1035
1258
  var AB_ATTRIBUTE = "data-prepr-ab";
1036
1259
  var AB_BUCKETS = ["A", "B"];
1037
1260
  var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
@@ -1053,13 +1276,18 @@ function renderBuckets(items, render, options) {
1053
1276
  abTest,
1054
1277
  index,
1055
1278
  track,
1056
- placeholder = false
1279
+ placeholder = false,
1280
+ defaultVariant = true
1057
1281
  } = options;
1058
1282
  const renderVariant = (variant, key) => {
1059
1283
  const resolved = resolveVariants(items, { decisions, bucket: variant });
1060
- const content = render(resolved, { index, placeholder });
1061
- const variantKeys = track ? variantKeysOf(resolved) : [];
1062
1284
  const ab = abTest && !bucket ? variant : void 0;
1285
+ const content = render(resolved, {
1286
+ index,
1287
+ placeholder,
1288
+ defaultVariant: defaultVariant && (ab === void 0 || ab === "A")
1289
+ });
1290
+ const variantKeys = track ? variantKeysOf(resolved) : [];
1063
1291
  if (variantKeys.length > 0) {
1064
1292
  return createElement(
1065
1293
  PreprVariantTracking,
@@ -1097,7 +1325,8 @@ function renderAllVariants(block, render, options) {
1097
1325
  decisions: /* @__PURE__ */ new Map([[group, [variant]]]),
1098
1326
  abTest: false,
1099
1327
  index: block.index,
1100
- track: options.track
1328
+ track: options.track,
1329
+ defaultVariant: variant === PREPR_DEFAULT_VARIANT
1101
1330
  })
1102
1331
  )
1103
1332
  );
@@ -1130,7 +1359,7 @@ function PreprStack({
1130
1359
  bucket,
1131
1360
  nonce,
1132
1361
  trackVariants = false,
1133
- aboveTheFold = 0
1362
+ aboveTheFold = 1
1134
1363
  }) {
1135
1364
  decisions.catch(() => void 0);
1136
1365
  const blocks = splitStack(items);
@@ -1153,7 +1382,8 @@ function PreprStack({
1153
1382
  nonce,
1154
1383
  dangerouslySetInnerHTML: { __html: AB_SWITCH_SCRIPT }
1155
1384
  }),
1156
- createElement(PreprAbSwitch)
1385
+ createElement(PreprAbSwitch),
1386
+ hasDecided && createElement(PreprPersonalizationSwitch, { refresh: false })
1157
1387
  ),
1158
1388
  ...blocks.map((block) => {
1159
1389
  if (decidedInProxy(block)) {
@@ -1202,7 +1432,7 @@ function createPreprClient2(options) {
1202
1432
  ...coreOptions
1203
1433
  });
1204
1434
  const seenGroups = /* @__PURE__ */ new Map();
1205
- return {
1435
+ const nextClient = {
1206
1436
  async query(document2, variables, queryOptions = {}) {
1207
1437
  const { personalize = false, ...rest } = queryOptions;
1208
1438
  const init = rest.init;
@@ -1243,6 +1473,12 @@ function createPreprClient2(options) {
1243
1473
  options2
1244
1474
  )
1245
1475
  };
1476
+ preprClientInternals.set(nextClient, {
1477
+ client,
1478
+ seenGroups,
1479
+ url: coreOptions.url
1480
+ });
1481
+ return nextClient;
1246
1482
  }
1247
1483
  export {
1248
1484
  PREPR_DECISIONS_COOKIE,
@@ -1250,6 +1486,7 @@ export {
1250
1486
  PreprError,
1251
1487
  PreprStack,
1252
1488
  createPreprClient2 as createPreprClient,
1489
+ createPreprProxy,
1253
1490
  decisionsFrom,
1254
1491
  isPersonalizedResponse,
1255
1492
  loadPreprSchema,
@@ -22,6 +22,7 @@ var nextjs_exports = {};
22
22
  __export(nextjs_exports, {
23
23
  PREPR_BUCKET_COOKIE: () => PREPR_BUCKET_COOKIE,
24
24
  PreprError: () => PreprError,
25
+ PreprPersonalizationSwitch: () => import_components.PreprPersonalizationSwitch,
25
26
  PreprToolbar: () => import_components.PreprToolbar,
26
27
  PreprTrackingPixel: () => import_components.PreprTrackingPixel,
27
28
  createPreprDraftRoute: () => createPreprDraftRoute,
@@ -43,7 +44,7 @@ module.exports = __toCommonJS(nextjs_exports);
43
44
  var import_server = require("next/server");
44
45
 
45
46
  // src/version.ts
46
- var VERSION = "0.6.0-alpha.1";
47
+ var VERSION = "0.6.0-alpha.3";
47
48
 
48
49
  // src/core/constants.ts
49
50
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -574,6 +575,7 @@ var import_components = require("./components.cjs");
574
575
  0 && (module.exports = {
575
576
  PREPR_BUCKET_COOKIE,
576
577
  PreprError,
578
+ PreprPersonalizationSwitch,
577
579
  PreprToolbar,
578
580
  PreprTrackingPixel,
579
581
  createPreprDraftRoute,
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { b as PreprFeatures, P as PreprHeaders, c as PreprToolbarProps, d as PreprToolbarComponentProps } from './server-CjpELYvq.cjs';
3
- export { a as PreprError, e as extractAccessToken, i as isPersonalizedResponse, v as validatePreprToken } from './server-CjpELYvq.cjs';
2
+ import { a as PreprFeatures, P as PreprHeaders, c as PreprToolbarProps, d as PreprToolbarComponentProps } from './server-BL-sqZvu.cjs';
3
+ export { b as PreprError, e as extractAccessToken, i as isPersonalizedResponse, v as validatePreprToken } from './server-BL-sqZvu.cjs';
4
4
 
5
5
  interface PreprMiddlewareOptions {
6
6
  /** Enable preview-mode headers (segment/AB overrides, preview bar). */
@@ -153,5 +153,34 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
153
153
  * `createPreprPreview` mounts imperatively.
154
154
  */
155
155
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
156
+ interface PreprPersonalizationSwitchProps {
157
+ /**
158
+ * Keep segment decisions fresh in the background. Default `true`. Leave it
159
+ * on for the instance in your root layout; `PreprStack` renders its own
160
+ * with `false`.
161
+ */
162
+ refresh?: boolean;
163
+ /** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
164
+ freshFor?: number;
165
+ }
166
+ /**
167
+ * Shows the segment variants decided in the proxy for the groups above the
168
+ * fold, before the page paints, and keeps those decisions fresh while the
169
+ * visitor browses: a short while after each page view, when the visitor
170
+ * points at a link to a personalized page, and when the tab comes back.
171
+ * Because the next page's decision is already in place when it opens, a
172
+ * visitor who joins a segment mid-visit sees it on their next page view,
173
+ * without anything on screen changing.
174
+ *
175
+ * Mount it once in the root layout. The background refreshes are answered by
176
+ * `createPreprProxy`; with a proxy that calls `resolveDecisions` itself, the
177
+ * first refresh gets a page back and refreshing stops for the browser
178
+ * session. The decisions themselves come from `createPreprProxy` or
179
+ * `resolveDecisions` in `proxy.ts`.
180
+ *
181
+ * @example
182
+ * <PreprPersonalizationSwitch />
183
+ */
184
+ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): null;
156
185
 
157
- export { PREPR_BUCKET_COOKIE, type PreprBucket, type PreprDraftOptions, type PreprMiddlewareOptions, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, type ResolvePreprBucketOptions, createPreprDraftRoute, createPreprMiddleware, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, preprDraftRedirect, resolvePreprBucket };
186
+ export { PREPR_BUCKET_COOKIE, type PreprBucket, type PreprDraftOptions, type PreprMiddlewareOptions, PreprPersonalizationSwitch, type PreprPersonalizationSwitchProps, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, type ResolvePreprBucketOptions, createPreprDraftRoute, createPreprMiddleware, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, preprDraftRedirect, resolvePreprBucket };
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { b as PreprFeatures, P as PreprHeaders, c as PreprToolbarProps, d as PreprToolbarComponentProps } from './server-CjpELYvq.js';
3
- export { a as PreprError, e as extractAccessToken, i as isPersonalizedResponse, v as validatePreprToken } from './server-CjpELYvq.js';
2
+ import { a as PreprFeatures, P as PreprHeaders, c as PreprToolbarProps, d as PreprToolbarComponentProps } from './server-BL-sqZvu.js';
3
+ export { b as PreprError, e as extractAccessToken, i as isPersonalizedResponse, v as validatePreprToken } from './server-BL-sqZvu.js';
4
4
 
5
5
  interface PreprMiddlewareOptions {
6
6
  /** Enable preview-mode headers (segment/AB overrides, preview bar). */
@@ -153,5 +153,34 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
153
153
  * `createPreprPreview` mounts imperatively.
154
154
  */
155
155
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
156
+ interface PreprPersonalizationSwitchProps {
157
+ /**
158
+ * Keep segment decisions fresh in the background. Default `true`. Leave it
159
+ * on for the instance in your root layout; `PreprStack` renders its own
160
+ * with `false`.
161
+ */
162
+ refresh?: boolean;
163
+ /** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
164
+ freshFor?: number;
165
+ }
166
+ /**
167
+ * Shows the segment variants decided in the proxy for the groups above the
168
+ * fold, before the page paints, and keeps those decisions fresh while the
169
+ * visitor browses: a short while after each page view, when the visitor
170
+ * points at a link to a personalized page, and when the tab comes back.
171
+ * Because the next page's decision is already in place when it opens, a
172
+ * visitor who joins a segment mid-visit sees it on their next page view,
173
+ * without anything on screen changing.
174
+ *
175
+ * Mount it once in the root layout. The background refreshes are answered by
176
+ * `createPreprProxy`; with a proxy that calls `resolveDecisions` itself, the
177
+ * first refresh gets a page back and refreshing stops for the browser
178
+ * session. The decisions themselves come from `createPreprProxy` or
179
+ * `resolveDecisions` in `proxy.ts`.
180
+ *
181
+ * @example
182
+ * <PreprPersonalizationSwitch />
183
+ */
184
+ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): null;
156
185
 
157
- export { PREPR_BUCKET_COOKIE, type PreprBucket, type PreprDraftOptions, type PreprMiddlewareOptions, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, type ResolvePreprBucketOptions, createPreprDraftRoute, createPreprMiddleware, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, preprDraftRedirect, resolvePreprBucket };
186
+ export { PREPR_BUCKET_COOKIE, type PreprBucket, type PreprDraftOptions, type PreprMiddlewareOptions, PreprPersonalizationSwitch, type PreprPersonalizationSwitchProps, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, type ResolvePreprBucketOptions, createPreprDraftRoute, createPreprMiddleware, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, preprDraftRedirect, resolvePreprBucket };