@preprio/toolkit 0.6.0-alpha.1 → 0.6.0-alpha.10

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 (55) hide show
  1. package/README.md +163 -61
  2. package/dist/astro/{chunk-O7N6GPAH.js → chunk-CG2QBHNU.js} +1 -1
  3. package/dist/astro/graphql.cjs +58 -10
  4. package/dist/astro/graphql.d.cts +33 -38
  5. package/dist/astro/graphql.d.ts +33 -38
  6. package/dist/astro/graphql.js +59 -11
  7. package/dist/astro/index.cjs +1 -1
  8. package/dist/astro/index.js +1 -1
  9. package/dist/{chunk-VI6QZTBM.js → chunk-LIDUJWWJ.js} +1 -1
  10. package/dist/{chunk-TBKSBXGH.js → chunk-M3RBYJ5J.js} +52 -5
  11. package/dist/{chunk-3JLWZQ37.js → chunk-SF6TN4JR.js} +1 -1
  12. package/dist/{chunk-YWIKKWBR.js → chunk-TN6SNAR2.js} +1 -1
  13. package/dist/graphql/codegen.cjs +51 -4
  14. package/dist/graphql/codegen.js +3 -3
  15. package/dist/graphql.cjs +89 -10
  16. package/dist/graphql.d.cts +69 -39
  17. package/dist/graphql.d.ts +69 -39
  18. package/dist/graphql.js +38 -9
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +3 -3
  23. package/dist/nextjs/chunk-B4GVZEOL.js +549 -0
  24. package/dist/nextjs/components.cjs +297 -81
  25. package/dist/nextjs/components.d.cts +26 -11
  26. package/dist/nextjs/components.d.ts +26 -11
  27. package/dist/nextjs/components.js +288 -73
  28. package/dist/nextjs/graphql.cjs +856 -266
  29. package/dist/nextjs/graphql.d.cts +184 -116
  30. package/dist/nextjs/graphql.d.ts +184 -116
  31. package/dist/nextjs/graphql.js +543 -266
  32. package/dist/nextjs/index.cjs +116 -54
  33. package/dist/nextjs/index.d.cts +61 -23
  34. package/dist/nextjs/index.d.ts +61 -23
  35. package/dist/nextjs/index.js +97 -299
  36. package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.cts} +1 -1
  37. package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.ts} +1 -1
  38. package/dist/nuxt/{chunk-IQDSBPDN.js → chunk-FMC3DP7N.js} +1 -1
  39. package/dist/nuxt/graphql.cjs +58 -10
  40. package/dist/nuxt/graphql.d.cts +33 -38
  41. package/dist/nuxt/graphql.d.ts +33 -38
  42. package/dist/nuxt/graphql.js +59 -11
  43. package/dist/nuxt/index.cjs +1 -1
  44. package/dist/nuxt/index.js +1 -1
  45. package/dist/react/components.cjs +1 -1
  46. package/dist/react/components.js +1 -1
  47. package/dist/sveltekit/{chunk-OKYZ6HPH.js → chunk-AJPKBAHO.js} +1 -1
  48. package/dist/sveltekit/graphql.cjs +58 -10
  49. package/dist/sveltekit/graphql.d.cts +33 -38
  50. package/dist/sveltekit/graphql.d.ts +33 -38
  51. package/dist/sveltekit/graphql.js +59 -11
  52. package/dist/sveltekit/index.cjs +1 -1
  53. package/dist/sveltekit/index.js +1 -1
  54. package/package.json +1 -1
  55. package/dist/nextjs/chunk-BQ6IGSH6.js +0 -231
@@ -1,10 +1,20 @@
1
1
  import {
2
+ PREPR_ANY_ITEM_TAG,
3
+ PREPR_SCHEMA_TAG,
2
4
  PreprError,
5
+ createPreprMiddleware,
3
6
  getPreprHeaders,
4
7
  getPreprHeadersFromHeaders,
5
8
  isPersonalizedResponse,
9
+ preprCacheTags,
10
+ preprDraftRedirect,
11
+ preprModelTag,
12
+ preprModelTagsFor,
6
13
  validatePreprToken
7
- } from "./chunk-BQ6IGSH6.js";
14
+ } from "./chunk-B4GVZEOL.js";
15
+
16
+ // src/nextjs/graphql.ts
17
+ import * as nextCache from "next/cache";
8
18
 
9
19
  // src/core/variants/constants.ts
10
20
  var PREPR_CONTEXT_ALIAS = "_preprContext";
@@ -93,7 +103,10 @@ function decisionSelectionSet(set, typeName, path, ctx) {
93
103
  path,
94
104
  ctx
95
105
  );
96
- if (pruned) {
106
+ if (!pruned) continue;
107
+ if (fragment.typeCondition.name.value === typeName) {
108
+ selections.push(...pruned.selections);
109
+ } else {
97
110
  selections.push(
98
111
  inlineFragment(fragment.typeCondition.name.value, [
99
112
  ...pruned.selections
@@ -103,13 +116,19 @@ function decisionSelectionSet(set, typeName, path, ctx) {
103
116
  continue;
104
117
  }
105
118
  if (selection.kind === "InlineFragment") {
119
+ const condition = selection.typeCondition?.name.value ?? typeName;
106
120
  const pruned = decisionSelectionSet(
107
121
  selection.selectionSet,
108
- selection.typeCondition?.name.value ?? typeName,
122
+ condition,
109
123
  path,
110
124
  ctx
111
125
  );
112
- if (pruned) selections.push({ ...selection, selectionSet: pruned });
126
+ if (!pruned) continue;
127
+ if (condition === typeName && !selection.directives?.length) {
128
+ selections.push(...pruned.selections);
129
+ } else {
130
+ selections.push({ ...selection, selectionSet: pruned });
131
+ }
113
132
  continue;
114
133
  }
115
134
  if (!selection.selectionSet) continue;
@@ -183,20 +202,58 @@ function toDecisionDocument(document2, schema, groupPaths, options) {
183
202
  const used = /* @__PURE__ */ new Set();
184
203
  collectVariables(pruned, used);
185
204
  collectVariables(operation.directives, used);
205
+ const shared = sharedContextFragment(schema);
186
206
  return {
187
207
  kind: "Document",
188
208
  definitions: [
209
+ ...shared ? [shared.definition] : [],
189
210
  {
190
211
  ...operation,
191
212
  name: operation.name && name(`${operation.name.value}Decision`),
192
213
  variableDefinitions: (operation.variableDefinitions ?? []).filter(
193
214
  (definition) => used.has(definition.variable.name.value)
194
215
  ),
195
- selectionSet: pruned
216
+ selectionSet: shared ? shared.apply(pruned) : pruned
196
217
  }
197
218
  ]
198
219
  };
199
220
  }
221
+ function sharedContextFragment(schema) {
222
+ const contextTypes = new Set(
223
+ Object.values(schema.types).flatMap(
224
+ (type) => type.hasContext ? [type.fields._context.type] : []
225
+ )
226
+ );
227
+ if (contextTypes.size !== 1) return null;
228
+ const [contextType] = contextTypes;
229
+ const fragmentName = "c";
230
+ const definition = {
231
+ kind: "FragmentDefinition",
232
+ name: name(fragmentName),
233
+ typeCondition: { kind: "NamedType", name: name(contextType) },
234
+ selectionSet: selectionSet(
235
+ ["kind", "group_id", "variant_id"].map(
236
+ (field) => ({ kind: "Field", name: name(field) })
237
+ )
238
+ )
239
+ };
240
+ const spread = {
241
+ kind: "FragmentSpread",
242
+ name: name(fragmentName)
243
+ };
244
+ const apply = (set) => selectionSet(
245
+ set.selections.map((selection) => {
246
+ if (selection.kind === "Field" && selection.alias?.value === PREPR_CONTEXT_ALIAS) {
247
+ return { ...selection, selectionSet: selectionSet([spread]) };
248
+ }
249
+ return "selectionSet" in selection && selection.selectionSet ? {
250
+ ...selection,
251
+ selectionSet: apply(selection.selectionSet)
252
+ } : selection;
253
+ })
254
+ );
255
+ return { definition, apply };
256
+ }
200
257
 
201
258
  // src/core/variants/probe-document.ts
202
259
  function probeSelectionSet(set, fragments) {
@@ -770,6 +827,7 @@ function createVaryProbe(run, options) {
770
827
  }
771
828
 
772
829
  // src/core/graphql/client.ts
830
+ var clientSchemas = /* @__PURE__ */ new WeakMap();
773
831
  function isParseableUrl(url) {
774
832
  try {
775
833
  new URL(url);
@@ -806,7 +864,7 @@ function createPreprClient(options) {
806
864
  ));
807
865
  const variantsDocuments = /* @__PURE__ */ new Map();
808
866
  const decisionDocuments = /* @__PURE__ */ new Map();
809
- return {
867
+ const client = {
810
868
  async query(document2, variables, queryOptions = {}) {
811
869
  const { variants, ...rest } = queryOptions;
812
870
  if (!variants) return run(document2, variables, rest);
@@ -818,10 +876,10 @@ function createPreprClient(options) {
818
876
  return run(derived, variables, rest);
819
877
  },
820
878
  async decide(document2, variables, queryOptions = {}) {
821
- const { from, topLevelOnly, ...rest } = queryOptions;
879
+ const { from, ...rest } = queryOptions;
822
880
  const paths = from === void 0 ? void 0 : personalizationPaths(from);
823
881
  if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
824
- const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : topLevelOnly ? "top" : "*";
882
+ const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : "*";
825
883
  let byPaths = decisionDocuments.get(document2);
826
884
  if (!byPaths) {
827
885
  byPaths = /* @__PURE__ */ new Map();
@@ -829,9 +887,7 @@ function createPreprClient(options) {
829
887
  }
830
888
  let derived = byPaths.get(signature);
831
889
  if (derived === void 0) {
832
- derived = toDecisionDocument(document2, await loadSchema(), paths, {
833
- topLevelOnly: !paths && topLevelOnly
834
- });
890
+ derived = toDecisionDocument(document2, await loadSchema(), paths);
835
891
  byPaths.set(signature, derived);
836
892
  }
837
893
  if (!derived) return /* @__PURE__ */ new Map();
@@ -845,6 +901,8 @@ function createPreprClient(options) {
845
901
  (vary) => vary === null || SEGMENTS_VARY.test(vary)
846
902
  )
847
903
  };
904
+ clientSchemas.set(client, loadSchema);
905
+ return client;
848
906
  }
849
907
 
850
908
  // src/core/graphql/schema-loader.ts
@@ -872,25 +930,62 @@ async function loadPreprSchema(url, options = {}) {
872
930
  }
873
931
 
874
932
  // src/nextjs/decisions.ts
933
+ var PREPR_DECIDE_HEADER = "Prepr-Decide";
934
+ var DEFAULT_DECISION = "*";
875
935
  var PREPR_DECISIONS_COOKIE = "prepr_decisions";
876
- var DECISIONS_MAX_GROUPS = 40;
877
- var SAFE_ID = /^[\w-]+$/;
878
- var SEEN_MAX_PAGES = 1e3;
879
- function parseDecisions(raw) {
880
- const out = /* @__PURE__ */ new Map();
881
- for (const pair of (raw ?? "").split("~")) {
882
- const at = pair.indexOf(":");
883
- const group = pair.slice(0, at);
884
- if (at < 1 || !SAFE_ID.test(group)) continue;
885
- out.set(
886
- group,
887
- pair.slice(at + 1).split("|").filter((variant) => SAFE_ID.test(variant))
888
- );
936
+ var DECISION_FRESH_MS = 1e4;
937
+ var DECISION_KEEP_MS = 18e5;
938
+ var DECISION_KEEP_STATIC_MS = 36e5;
939
+ var STORED_MAX_ENTRIES = 20;
940
+ var STORED_MAX_LENGTH = 3e3;
941
+ function readStoredDecisions(raw) {
942
+ const stored = /* @__PURE__ */ Object.create(null);
943
+ if (!raw) return stored;
944
+ try {
945
+ const parsed = JSON.parse(raw);
946
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
947
+ return stored;
948
+ for (const [key, entry] of Object.entries(parsed)) {
949
+ if (key.startsWith("/") && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number") {
950
+ stored[key] = [entry[0], entry[1]];
951
+ }
952
+ }
953
+ } catch {
954
+ }
955
+ return stored;
956
+ }
957
+ function usableDecision(stored, key, now) {
958
+ const entry = stored[key];
959
+ if (!entry) return void 0;
960
+ const [value, at] = entry;
961
+ const age = now - at;
962
+ const keep = value === DEFAULT_DECISION ? DECISION_FRESH_MS : value === "" ? DECISION_KEEP_STATIC_MS : DECISION_KEEP_MS;
963
+ return age >= 0 && age < keep ? value : void 0;
964
+ }
965
+ function writeStoredDecisions(stored, key, value, now) {
966
+ const kept = usableDecision(stored, key, now);
967
+ const entries = Object.entries(stored).filter(
968
+ ([stale]) => stale !== key && usableDecision(stored, stale, now) !== void 0
969
+ );
970
+ entries.push([
971
+ key,
972
+ value === DEFAULT_DECISION && kept !== void 0 && kept !== DEFAULT_DECISION ? stored[key] : [value, now]
973
+ ]);
974
+ entries.sort((a, b) => b[1][1] - a[1][1]);
975
+ entries.length = Math.min(entries.length, STORED_MAX_ENTRIES);
976
+ let json = JSON.stringify(Object.fromEntries(entries));
977
+ while (entries.length > 1 && encodeURIComponent(json).length > STORED_MAX_LENGTH) {
978
+ entries.pop();
979
+ json = JSON.stringify(Object.fromEntries(entries));
889
980
  }
890
- return out;
981
+ return json;
891
982
  }
983
+ var DECISIONS_MAX_GROUPS = 40;
984
+ var SAFE_ID = /^[\w-]+$/;
892
985
  function serializeDecisions(decisions) {
893
- return [...decisions].slice(0, DECISIONS_MAX_GROUPS).map(([group, variants]) => `${group}:${variants.join("|")}`).join("~");
986
+ return [...decisions].filter(([group]) => SAFE_ID.test(group)).slice(0, DECISIONS_MAX_GROUPS).map(
987
+ ([group, variants]) => `${group}:${variants.filter((v) => SAFE_ID.test(v)).join("|")}`
988
+ ).join("~");
894
989
  }
895
990
  function forwardedHeaders(request, response) {
896
991
  const overridden = response.headers.get("x-middleware-override-headers");
@@ -902,288 +997,448 @@ function forwardedHeaders(request, response) {
902
997
  }
903
998
  return getPreprHeadersFromHeaders(headers);
904
999
  }
905
- async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
1000
+ var preprClientInternals = /* @__PURE__ */ new WeakMap();
1001
+ async function decideView(client, headers, page, timeoutMs = 1e3) {
1002
+ const init = { signal: AbortSignal.timeout(timeoutMs) };
906
1003
  try {
907
- if (options?.onlyIf && !await options.onlyIf()) return;
908
- if (!await client.hasSegments(document2, variables)) return;
909
- const headers = mergePreprHeaders(forwardedHeaders(request, response));
910
- const stored = parseDecisions(
911
- request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1004
+ const decided = page.decision ? decisionsFrom(
1005
+ (await client.query(page.decision, page.variables, { headers, init })).data
1006
+ ) : await client.decide(page.document, page.variables, { headers, init });
1007
+ return serializeDecisions(decided);
1008
+ } catch (error) {
1009
+ if (isFrameworkSignal(error)) throw error;
1010
+ return DEFAULT_DECISION;
1011
+ }
1012
+ }
1013
+
1014
+ // src/nextjs/proxy.ts
1015
+ import { NextResponse } from "next/server";
1016
+ function isRouterFetch(request) {
1017
+ return request.headers.has("rsc") || request.headers.has("next-url") || request.headers.get("sec-fetch-dest") === "empty";
1018
+ }
1019
+ var STATIC_PAGE_MS = 3e5;
1020
+ var STATIC_PAGES_MAX = 1e3;
1021
+ var staticPages = /* @__PURE__ */ new Map();
1022
+ function isPagePath(pathname) {
1023
+ return !pathname.startsWith("/_next/") && !pathname.startsWith("/api/") && !/\.[^/]*$/.test(pathname);
1024
+ }
1025
+ function createPreprProxy(options) {
1026
+ const internals = preprClientInternals.get(options.client);
1027
+ if (!internals) {
1028
+ throw new PreprError(
1029
+ "createPreprProxy needs a client made by createPreprClient from @preprio/toolkit/nextjs/graphql",
1030
+ "CONTEXT_ERROR",
1031
+ "createPreprProxy"
912
1032
  );
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);
1033
+ }
1034
+ const { client } = internals;
1035
+ const preview = options.preview ?? false;
1036
+ const allowed = async (request) => {
1037
+ if (!options.onlyIf) return true;
1038
+ try {
1039
+ return await options.onlyIf(request);
1040
+ } catch {
1041
+ return false;
918
1042
  }
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))) {
925
- return;
1043
+ };
1044
+ const lookup = (target) => {
1045
+ try {
1046
+ return options.page(target) ?? null;
1047
+ } catch {
1048
+ return null;
926
1049
  }
927
- const decided = await client.decide(document2, variables, {
928
- topLevelOnly: true,
929
- headers,
930
- init: { signal: AbortSignal.timeout(options?.timeoutMs ?? 1e3) }
931
- });
932
- if (!known && byVariables.size >= SEEN_MAX_PAGES) {
933
- byVariables.delete(byVariables.keys().next().value);
1050
+ };
1051
+ async function answerDecision(request, response, target) {
1052
+ let decision = DEFAULT_DECISION;
1053
+ const site = request.headers.get("sec-fetch-site");
1054
+ if (target && request.method === "GET" && (site === null || site === "same-origin") && await allowed(request)) {
1055
+ decision = await decideView(
1056
+ client,
1057
+ mergePreprHeaders(forwardedHeaders(request, response)),
1058
+ target,
1059
+ options.timeoutMs
1060
+ );
934
1061
  }
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
- );
1062
+ const answer = new NextResponse(decision, {
1063
+ headers: {
1064
+ "Cache-Control": "private, no-store",
1065
+ "Content-Type": "text/plain; charset=utf-8"
945
1066
  }
1067
+ });
1068
+ for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
1069
+ return answer;
1070
+ }
1071
+ async function decidePageLoad(request, response, target) {
1072
+ const { basePath, pathname, search } = request.nextUrl;
1073
+ const key = basePath + pathname + search;
1074
+ const now = Date.now();
1075
+ if ((staticPages.get(key) ?? 0) > now) return response;
1076
+ const stored = readStoredDecisions(
1077
+ request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1078
+ );
1079
+ if (!preview && usableDecision(stored, key, now) !== void 0)
1080
+ return response;
1081
+ if (!await allowed(request)) return response;
1082
+ const decision = await decideView(
1083
+ client,
1084
+ mergePreprHeaders(forwardedHeaders(request, response)),
1085
+ target,
1086
+ options.timeoutMs
1087
+ );
1088
+ if (decision === "") {
1089
+ if (staticPages.size >= STATIC_PAGES_MAX) staticPages.clear();
1090
+ staticPages.set(key, now + STATIC_PAGE_MS);
1091
+ return response;
946
1092
  }
947
- for (const [group, variants] of stored) {
948
- if (!next.has(group)) next.set(group, variants);
1093
+ response.cookies.set(
1094
+ PREPR_DECISIONS_COOKIE,
1095
+ writeStoredDecisions(stored, key, decision, now),
1096
+ {
1097
+ path: "/",
1098
+ maxAge: 3600,
1099
+ sameSite: "lax",
1100
+ secure: request.nextUrl.protocol === "https:"
1101
+ }
1102
+ );
1103
+ return response;
1104
+ }
1105
+ return async (request) => {
1106
+ const rendersPage = (request.method === "GET" || request.method === "HEAD") && isPagePath(request.nextUrl.pathname);
1107
+ const target = rendersPage ? lookup(request.nextUrl) : null;
1108
+ const decisionRequest = request.headers.has(PREPR_DECIDE_HEADER);
1109
+ if (target && preview && !decisionRequest) {
1110
+ const draft = preprDraftRedirect(request, { preview });
1111
+ if (draft) return draft;
949
1112
  }
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
1113
+ const response = createPreprMiddleware(request, {
1114
+ preview,
1115
+ features: options.features
956
1116
  });
957
- } catch (error) {
958
- if (isFrameworkSignal(error)) throw error;
959
- }
1117
+ if (decisionRequest) return answerDecision(request, response, target);
1118
+ if (target && request.method === "GET" && !isRouterFetch(request))
1119
+ return decidePageLoad(request, response, target);
1120
+ return response;
1121
+ };
960
1122
  }
961
1123
 
962
1124
  // src/nextjs/stack.ts
963
1125
  import {
964
1126
  createElement,
965
- Fragment,
966
- Suspense
1127
+ Fragment
967
1128
  } from "react";
968
1129
 
969
- // src/nextjs/ab-switch.ts
970
- function applyPreprAbBucket() {
971
- const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
972
- const bucket = match ? match[1] : "A";
1130
+ // src/nextjs/decision-switch.ts
1131
+ function applyPreprDecisions(decision, options = {}) {
973
1132
  const state = window;
974
- if (state.__preprAbBucket === bucket) return;
975
- state.__preprAbBucket = bucket;
976
- const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
977
- try {
978
- if (!state.__preprAbSheet) {
979
- const sheet = new CSSStyleSheet();
980
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
981
- state.__preprAbSheet = sheet;
982
- }
983
- state.__preprAbSheet.replaceSync(css);
984
- } catch {
985
- if (!state.__preprAbSheet) {
986
- state.__preprAbSheet = document.createElement("style");
987
- document.head.appendChild(state.__preprAbSheet);
988
- }
989
- state.__preprAbSheet.textContent = css;
990
- }
991
- }
992
- function applyPreprDecisions() {
993
- const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
994
- let raw;
995
- try {
996
- raw = match ? decodeURIComponent(match[1]) : "";
997
- } catch {
998
- raw = "";
1133
+ const key = location.pathname + location.search;
1134
+ const previous = state.__preprDecision;
1135
+ let value = decision;
1136
+ if (value !== void 0 && value !== "*" && options.refresh && previous && previous.key === key && previous.value !== "*") {
1137
+ const pairs = (from) => {
1138
+ const out = {};
1139
+ from.split("~").forEach((pair) => {
1140
+ const colon = pair.indexOf(":");
1141
+ if (colon > 0) out[pair.slice(0, colon)] = pair;
1142
+ });
1143
+ return out;
1144
+ };
1145
+ const before = pairs(previous.value);
1146
+ const after = pairs(value);
1147
+ document.querySelectorAll("[data-prepr-v][data-prepr-show]").forEach((element) => {
1148
+ const range = document.createRange();
1149
+ range.selectNodeContents(element);
1150
+ if (range.getBoundingClientRect().top >= window.innerHeight) return;
1151
+ (element.getAttribute("data-prepr-v") || "").split(" ").forEach((token) => {
1152
+ const group = token.slice(0, token.indexOf(":"));
1153
+ if (before[group]) after[group] = before[group];
1154
+ });
1155
+ });
1156
+ value = Object.keys(after).map((group) => after[group]).join("~");
999
1157
  }
1000
- const state = window;
1001
- if (state.__preprDecisions === raw) return;
1002
- state.__preprDecisions = raw;
1003
- const id = /^[\w-]+$/;
1004
- let css = "";
1005
- raw.split("~").forEach((pair) => {
1006
- const at = pair.indexOf(":");
1007
- const group = pair.slice(0, at);
1008
- if (at < 1 || !id.test(group)) return;
1009
- const selector = '[data-prepr-group="' + group + '"]';
1010
- css += selector + "{display:none!important}";
1011
- pair.slice(at + 1).split("|").forEach((variant) => {
1012
- if (id.test(variant)) {
1013
- css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
1014
- }
1158
+ if (value !== void 0) state.__preprDecision = { key, value };
1159
+ const current = state.__preprDecision;
1160
+ if (!current || current.key !== key) return false;
1161
+ document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
1162
+ marker.setAttribute("data-prepr-decided", "");
1163
+ });
1164
+ const defaults = current.value === "*";
1165
+ const chosen = {};
1166
+ current.value.split("~").forEach((pair) => {
1167
+ const colon = pair.indexOf(":");
1168
+ if (colon > 0)
1169
+ chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
1170
+ });
1171
+ document.querySelectorAll("[data-prepr-v]").forEach((element) => {
1172
+ const nested = element.hasAttribute("data-prepr-nested");
1173
+ const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
1174
+ const colon = token.indexOf(":");
1175
+ const variant = token.slice(colon + 1);
1176
+ const variants = chosen[token.slice(0, colon)];
1177
+ return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
1015
1178
  });
1179
+ if (show) element.setAttribute("data-prepr-show", "");
1180
+ else element.removeAttribute("data-prepr-show");
1016
1181
  });
1017
- try {
1018
- if (!state.__preprDecisionSheet) {
1019
- const sheet = new CSSStyleSheet();
1020
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
1021
- state.__preprDecisionSheet = sheet;
1182
+ return true;
1183
+ }
1184
+ function requestPreprDecision(key, options = {}) {
1185
+ const fresh = 1e4;
1186
+ const keep = 18e5;
1187
+ const keepStatic = 36e5;
1188
+ const state = window;
1189
+ const asking = state.__preprAsking = state.__preprAsking || {};
1190
+ const read = () => {
1191
+ const all = {};
1192
+ try {
1193
+ const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
1194
+ const parsed = match ? JSON.parse(decodeURIComponent(match[1])) : null;
1195
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
1196
+ Object.keys(parsed).forEach((stored) => {
1197
+ const entry = parsed[stored];
1198
+ if (stored.charAt(0) === "/" && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number")
1199
+ all[stored] = [entry[0], entry[1]];
1200
+ });
1201
+ } catch {
1022
1202
  }
1023
- state.__preprDecisionSheet.replaceSync(css);
1024
- } catch {
1025
- if (!state.__preprDecisionSheet) {
1026
- state.__preprDecisionSheet = document.createElement("style");
1027
- document.head.appendChild(state.__preprDecisionSheet);
1203
+ return all;
1204
+ };
1205
+ const usable = (entry, now) => {
1206
+ if (!entry) return false;
1207
+ const age = now - entry[1];
1208
+ return age >= 0 && age < (entry[0] === "*" ? fresh : entry[0] === "" ? keepStatic : keep);
1209
+ };
1210
+ const store = (answer) => {
1211
+ const all = read();
1212
+ const now = Date.now();
1213
+ const kept = all[key];
1214
+ const entries = Object.keys(all).filter((stored) => stored !== key && usable(all[stored], now)).map((stored) => [stored, all[stored]]);
1215
+ entries.push([
1216
+ key,
1217
+ answer === "*" && kept && usable(kept, now) && kept[0] !== "*" ? kept : [answer, now]
1218
+ ]);
1219
+ entries.sort((a, b) => b[1][1] - a[1][1]);
1220
+ entries.length = Math.min(entries.length, 20);
1221
+ const encode = () => {
1222
+ const out = {};
1223
+ entries.forEach((entry) => {
1224
+ out[entry[0]] = entry[1];
1225
+ });
1226
+ return encodeURIComponent(JSON.stringify(out));
1227
+ };
1228
+ let value = encode();
1229
+ while (entries.length > 1 && value.length > 3e3) {
1230
+ entries.pop();
1231
+ value = encode();
1232
+ }
1233
+ document.cookie = "prepr_decisions=" + value + "; path=/; max-age=3600; samesite=lax" + (location.protocol === "https:" ? "; secure" : "");
1234
+ };
1235
+ const ask = () => {
1236
+ if (!asking[key]) {
1237
+ asking[key] = fetch(key, {
1238
+ headers: { "Prepr-Decide": "1" },
1239
+ cache: "no-store",
1240
+ credentials: "same-origin",
1241
+ // The proxy gives up on Prepr after its own `timeoutMs`; this only
1242
+ // covers a proxy that never answers.
1243
+ signal: AbortSignal.timeout ? AbortSignal.timeout(6e3) : void 0
1244
+ }).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((answer) => {
1245
+ delete asking[key];
1246
+ try {
1247
+ store(answer);
1248
+ } catch {
1249
+ }
1250
+ return answer;
1251
+ });
1028
1252
  }
1029
- state.__preprDecisionSheet.textContent = css;
1253
+ return asking[key];
1254
+ };
1255
+ const last = read()[key];
1256
+ if (last && !(last[0] === "" && options.personalized) && usable(last, Date.now())) {
1257
+ const value = last[0];
1258
+ if (value !== "" && Date.now() - last[1] >= fresh) {
1259
+ ask().then((answer) => {
1260
+ if (answer !== "*" && answer !== value && options.onChange)
1261
+ options.onChange(answer);
1262
+ });
1263
+ }
1264
+ return { result: value, value: Promise.resolve(value) };
1030
1265
  }
1266
+ const pending = {
1267
+ value: ask()
1268
+ };
1269
+ pending.value.then((answer) => {
1270
+ pending.result = answer;
1271
+ });
1272
+ return pending;
1273
+ }
1274
+ function startPreprDecision() {
1275
+ const state = window;
1276
+ const key = location.pathname + location.search;
1277
+ const apply = (answer, refresh) => {
1278
+ if (location.pathname + location.search === key)
1279
+ state.__preprApply(answer, { refresh });
1280
+ };
1281
+ const pending = state.__preprRequest(key, {
1282
+ onChange: (answer) => apply(answer, true),
1283
+ personalized: true
1284
+ });
1285
+ if (pending.result !== void 0) apply(pending.result);
1286
+ else pending.value.then((answer) => apply(answer));
1031
1287
  }
1032
1288
 
1033
1289
  // src/nextjs/stack.ts
1034
- import { PreprAbSwitch, PreprVariantTracking } from "./components.js";
1035
- var AB_ATTRIBUTE = "data-prepr-ab";
1036
- var AB_BUCKETS = ["A", "B"];
1037
- var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
1038
- var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
1039
- var GROUP_ATTRIBUTE = "data-prepr-group";
1040
- var VARIANT_ATTRIBUTE = "data-prepr-variant";
1290
+ import { PreprDecisionApplier, PreprVariantTracking } from "./components.js";
1291
+ var VARIANT_ATTRIBUTE = "data-prepr-v";
1041
1292
  var DEFAULT_ATTRIBUTE = "data-prepr-default";
1042
- var GROUP_BASE_CSS = `[${GROUP_ATTRIBUTE}]{display:contents}[${GROUP_ATTRIBUTE}]:not([${DEFAULT_ATTRIBUTE}]){display:none}`;
1293
+ var NESTED_ATTRIBUTE = "data-prepr-nested";
1294
+ var GROUP_BASE_CSS = `[${VARIANT_ATTRIBUTE}]{display:none}[${VARIANT_ATTRIBUTE}][data-prepr-show]{display:contents}[data-prepr-decision]:not([data-prepr-decided])~[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents;visibility:hidden}`;
1295
+ var NOSCRIPT_HTML = `<style>[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents!important;visibility:visible!important}</style>`;
1296
+ var DECISION_SCRIPT = `window.__preprApply=${applyPreprDecisions.toString()};window.__preprRequest=${requestPreprDecision.toString()};(${startPreprDecision.toString()})();`;
1297
+ var APPLY_SCRIPT = "__preprApply()";
1043
1298
  function variantKeysOf(items) {
1044
1299
  return items.flatMap((item) => {
1045
- const key = preprVariantProps(item)["data-prepr-variant-key"];
1300
+ const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
1046
1301
  return key ? [key] : [];
1047
1302
  });
1048
1303
  }
1049
- function renderBuckets(items, render, options) {
1050
- const {
1051
- bucket,
1052
- decisions,
1053
- abTest,
1054
- index,
1055
- track,
1056
- placeholder = false
1057
- } = options;
1058
- const renderVariant = (variant, key) => {
1059
- const resolved = resolveVariants(items, { decisions, bucket: variant });
1060
- const content = render(resolved, { index, placeholder });
1061
- const variantKeys = track ? variantKeysOf(resolved) : [];
1062
- const ab = abTest && !bucket ? variant : void 0;
1063
- if (variantKeys.length > 0) {
1064
- return createElement(
1065
- PreprVariantTracking,
1066
- { key, variantKeys, ab },
1067
- content
1068
- );
1304
+ function nestedKeyOf(value) {
1305
+ if (!value || typeof value !== "object") return void 0;
1306
+ for (const child of Object.values(value)) {
1307
+ const items = Array.isArray(child) ? child : [child];
1308
+ for (const item of items) {
1309
+ const key = contextOf(item)?.variant_key ?? (item && nestedKeyOf(item));
1310
+ if (key) return key;
1311
+ }
1312
+ }
1313
+ return void 0;
1314
+ }
1315
+ function renderItems(items, render, options) {
1316
+ const resolved = resolveVariants(items, {
1317
+ decisions: options.decisions,
1318
+ bucket: options.bucket
1319
+ });
1320
+ const content = render(resolved, {
1321
+ index: options.index,
1322
+ defaultVariant: options.defaultVariant
1323
+ });
1324
+ const variantKeys = options.track ? variantKeysOf(resolved) : [];
1325
+ return variantKeys.length > 0 ? createElement(PreprVariantTracking, { variantKeys }, content) : content;
1326
+ }
1327
+ function decidedGroups(items, bucket) {
1328
+ const groups = /* @__PURE__ */ new Map();
1329
+ const walk = (value) => {
1330
+ if (Array.isArray(value)) return value.forEach(walk);
1331
+ if (!value || typeof value !== "object") return;
1332
+ const ctx = contextOf(value);
1333
+ const decided = ctx?.kind === "PERSONALIZATION" || ctx?.kind === "AB_TEST" && !bucket;
1334
+ if (decided && ctx.variant_id) {
1335
+ const group = groups.get(ctx.group_id) ?? {
1336
+ variants: /* @__PURE__ */ new Set(),
1337
+ fallback: ctx.kind === "AB_TEST" ? "A" : PREPR_DEFAULT_VARIANT
1338
+ };
1339
+ group.variants.add(ctx.variant_id);
1340
+ groups.set(ctx.group_id, group);
1069
1341
  }
1070
- if (ab) return createElement("div", { key, [AB_ATTRIBUTE]: ab }, content);
1071
- return content;
1342
+ Object.values(value).forEach(walk);
1072
1343
  };
1073
- if (!abTest || bucket) return renderVariant(bucket);
1074
- return AB_BUCKETS.map((variant) => renderVariant(variant, variant));
1075
- }
1076
- function renderAllVariants(block, render, options) {
1077
- const group = block.key.slice("group:".length);
1078
- const variants = [
1079
- ...new Set(
1080
- block.items.flatMap((item) => {
1081
- const ctx = item[PREPR_CONTEXT_ALIAS];
1082
- return ctx?.variant_id ? [ctx.variant_id] : [];
1083
- })
1084
- )
1085
- ];
1086
- return variants.map(
1087
- (variant) => createElement(
1344
+ walk(items);
1345
+ return groups;
1346
+ }
1347
+ function renderVariants(block, groups, render, options) {
1348
+ const top = block.key.startsWith("group:") ? block.key.slice("group:".length) : void 0;
1349
+ const nested = !(top !== void 0 && groups.size === 1);
1350
+ const choices = [...groups].map(([group, { variants, fallback }]) => {
1351
+ const ids = nested ? [fallback, ...variants] : [...variants];
1352
+ return [...new Set(ids)].map((variant) => [group, variant]);
1353
+ });
1354
+ const combinations = choices.reduce(
1355
+ (all, pairs) => all.flatMap((combo) => pairs.map((p) => [...combo, p])),
1356
+ [[]]
1357
+ );
1358
+ return combinations.map((combination) => {
1359
+ const pairs = combination.map(([group, variant]) => `${group}:${variant}`);
1360
+ const isDefault = combination.every(
1361
+ ([group, variant]) => variant === groups.get(group).fallback
1362
+ );
1363
+ return createElement(
1088
1364
  "div",
1089
1365
  {
1090
- key: variant,
1091
- [GROUP_ATTRIBUTE]: group,
1092
- [VARIANT_ATTRIBUTE]: variant,
1093
- ...variant === PREPR_DEFAULT_VARIANT ? { [DEFAULT_ATTRIBUTE]: "" } : {}
1366
+ key: pairs.join(" "),
1367
+ [VARIANT_ATTRIBUTE]: pairs.join(" "),
1368
+ ...isDefault ? { [DEFAULT_ATTRIBUTE]: "" } : {},
1369
+ ...nested ? { [NESTED_ATTRIBUTE]: "" } : {},
1370
+ // The decision script marks the shown wrapper before hydration.
1371
+ suppressHydrationWarning: true
1094
1372
  },
1095
- renderBuckets(block.items, render, {
1373
+ renderItems(block.items, render, {
1096
1374
  bucket: options.bucket,
1097
- decisions: /* @__PURE__ */ new Map([[group, [variant]]]),
1098
- abTest: false,
1375
+ decisions: new Map(combination.map(([g, v]) => [g, [v]])),
1099
1376
  index: block.index,
1100
- track: options.track
1377
+ track: options.track,
1378
+ defaultVariant: isDefault
1101
1379
  })
1102
- )
1103
- );
1104
- }
1105
- async function PersonalizedBlock({
1106
- items,
1107
- decisions,
1108
- bucket,
1109
- abTest,
1110
- index,
1111
- render,
1112
- track
1113
- }) {
1114
- const decided = await decisions.catch((error) => {
1115
- if (isFrameworkSignal(error)) throw error;
1116
- return void 0;
1117
- });
1118
- return renderBuckets(items, render, {
1119
- bucket,
1120
- decisions: decided,
1121
- abTest,
1122
- index,
1123
- track
1380
+ );
1124
1381
  });
1125
1382
  }
1126
1383
  function PreprStack({
1127
1384
  items,
1128
- decisions,
1129
1385
  render,
1130
1386
  bucket,
1131
1387
  nonce,
1132
- trackVariants = false,
1133
- aboveTheFold = 0
1388
+ trackVariants = false
1134
1389
  }) {
1135
- decisions.catch(() => void 0);
1136
- const blocks = splitStack(items);
1137
- const decidedInProxy = (block) => block.index < aboveTheFold && block.personalized && !block.abTest && block.key.startsWith("group:");
1138
- const hasDecided = blocks.some(decidedInProxy);
1139
- const needsSwitch = !bucket && blocks.some((block) => block.abTest) || hasDecided;
1390
+ const blocks = splitStack(items).map((block) => ({
1391
+ ...block,
1392
+ groups: decidedGroups(block.items, bucket)
1393
+ }));
1394
+ const varies = blocks.some((block) => block.groups.size > 0);
1140
1395
  return createElement(
1141
1396
  Fragment,
1142
1397
  null,
1143
- needsSwitch && createElement(
1398
+ varies && createElement(
1144
1399
  Fragment,
1145
- { key: "prepr-ab" },
1400
+ { key: "prepr-switch" },
1146
1401
  createElement("style", {
1147
1402
  nonce,
1148
- dangerouslySetInnerHTML: {
1149
- __html: AB_BASE_CSS + (hasDecided ? GROUP_BASE_CSS : "")
1150
- }
1403
+ dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
1404
+ }),
1405
+ createElement("noscript", {
1406
+ dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
1151
1407
  }),
1152
1408
  createElement("script", {
1153
1409
  nonce,
1154
- dangerouslySetInnerHTML: { __html: AB_SWITCH_SCRIPT }
1410
+ dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
1155
1411
  }),
1156
- createElement(PreprAbSwitch)
1412
+ createElement(PreprDecisionApplier),
1413
+ // Marked decided by the decision switch; never laid out.
1414
+ createElement("template", {
1415
+ "data-prepr-decision": "",
1416
+ suppressHydrationWarning: true
1417
+ })
1157
1418
  ),
1158
1419
  ...blocks.map((block) => {
1159
- if (decidedInProxy(block)) {
1420
+ if (block.groups.size === 0) {
1160
1421
  return createElement(
1161
1422
  Fragment,
1162
1423
  { key: block.key },
1163
- renderAllVariants(block, render, { bucket, track: trackVariants })
1424
+ renderItems(block.items, render, {
1425
+ bucket,
1426
+ index: block.index,
1427
+ track: trackVariants,
1428
+ defaultVariant: true
1429
+ })
1164
1430
  );
1165
1431
  }
1166
- const fallback = renderBuckets(block.items, render, {
1167
- bucket,
1168
- abTest: block.abTest,
1169
- index: block.index,
1170
- track: trackVariants && !block.personalized,
1171
- placeholder: block.personalized
1172
- });
1173
- if (!block.personalized) {
1174
- return createElement(Fragment, { key: block.key }, fallback);
1175
- }
1176
1432
  return createElement(
1177
- Suspense,
1178
- { key: block.key, fallback },
1179
- createElement(PersonalizedBlock, {
1180
- items: block.items,
1181
- decisions,
1433
+ Fragment,
1434
+ { key: block.key },
1435
+ renderVariants(block, block.groups, render, {
1182
1436
  bucket,
1183
- abTest: block.abTest,
1184
- index: block.index,
1185
- render,
1186
1437
  track: trackVariants
1438
+ }),
1439
+ createElement("script", {
1440
+ nonce,
1441
+ dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
1187
1442
  })
1188
1443
  );
1189
1444
  })
@@ -1195,33 +1450,59 @@ function createPreprClient2(options) {
1195
1450
  const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
1196
1451
  const client = createPreprClient({
1197
1452
  // The schema introspection goes through the Data Cache: shared across
1198
- // server instances, refreshed hourly and by revalidateTag on publish.
1453
+ // server instances, refreshed hourly and by a `schema.changed` webhook.
1199
1454
  schemaInit: {
1200
- next: { tags: cacheTags, revalidate: 3600 }
1455
+ next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
1201
1456
  },
1202
1457
  ...coreOptions
1203
1458
  });
1204
- const seenGroups = /* @__PURE__ */ new Map();
1205
- return {
1459
+ const nextClient = {
1206
1460
  async query(document2, variables, queryOptions = {}) {
1207
- const { personalize = false, ...rest } = queryOptions;
1208
- const init = rest.init;
1209
- const tagged = {
1461
+ const { personalize = false, tagItems = false, ...rest } = queryOptions;
1462
+ const { next, ...init } = rest.init ?? {};
1463
+ if (tagItems && typeof nextCache.cacheTag !== "function") {
1464
+ throw new Error("tagItems requires Next.js 16 or later.");
1465
+ }
1466
+ const options2 = {
1210
1467
  ...rest,
1211
- init: {
1468
+ // With tagItems, `next` is dropped too: tags or revalidate alongside
1469
+ // `no-store` are a conflict Next.js resolves by ignoring the store.
1470
+ // Otherwise tags are attached unconditionally; the core client drops
1471
+ // them again whenever the request ends up `no-store` (personalized,
1472
+ // per-request headers, or preview).
1473
+ init: tagItems ? { ...init, cache: "no-store" } : {
1212
1474
  ...init,
1213
1475
  next: {
1214
- tags: cacheTags,
1215
1476
  ...revalidate === void 0 ? {} : { revalidate },
1216
- ...init?.next
1477
+ ...next,
1478
+ // Merged, so a per-query tag never drops the client's own,
1479
+ // which the publish webhook relies on.
1480
+ tags: [...cacheTags, ...next?.tags ?? []]
1217
1481
  }
1218
1482
  }
1219
1483
  };
1220
- if (!personalize) return client.query(document2, variables, tagged);
1221
- return client.query(document2, variables, {
1222
- ...tagged,
1223
- headers: mergePreprHeaders(await getPreprHeaders(), rest.headers)
1224
- });
1484
+ if (personalize) {
1485
+ options2.headers = mergePreprHeaders(
1486
+ await getPreprHeaders(),
1487
+ rest.headers
1488
+ );
1489
+ }
1490
+ const result = await client.query(document2, variables, options2);
1491
+ if (tagItems) {
1492
+ let modelTags;
1493
+ try {
1494
+ modelTags = preprModelTagsFor(
1495
+ documentOf(document2),
1496
+ result.data,
1497
+ await clientSchemas.get(client)()
1498
+ );
1499
+ } catch {
1500
+ modelTags = [PREPR_ANY_ITEM_TAG];
1501
+ }
1502
+ nextCache.cacheTag(...cacheTags, ...next?.tags ?? [], ...modelTags);
1503
+ nextCache.cacheTag(...preprCacheTags(result.data));
1504
+ }
1505
+ return result;
1225
1506
  },
1226
1507
  async decide(document2, variables, queryOptions = {}) {
1227
1508
  return client.decide(document2, variables, {
@@ -1232,27 +1513,23 @@ function createPreprClient2(options) {
1232
1513
  )
1233
1514
  });
1234
1515
  },
1235
- hasAbTests: (document2, variables) => client.hasAbTests(document2, variables),
1236
- resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
1237
- client,
1238
- seenGroups,
1239
- request,
1240
- response,
1241
- document2,
1242
- variables,
1243
- options2
1244
- )
1516
+ hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
1245
1517
  };
1518
+ preprClientInternals.set(nextClient, { client });
1519
+ return nextClient;
1246
1520
  }
1247
1521
  export {
1248
- PREPR_DECISIONS_COOKIE,
1522
+ PREPR_ANY_ITEM_TAG,
1249
1523
  PREPR_DEFAULT_VARIANT,
1250
1524
  PreprError,
1251
1525
  PreprStack,
1252
1526
  createPreprClient2 as createPreprClient,
1527
+ createPreprProxy,
1253
1528
  decisionsFrom,
1254
1529
  isPersonalizedResponse,
1255
1530
  loadPreprSchema,
1531
+ preprCacheTags,
1532
+ preprModelTag,
1256
1533
  preprVariantProps,
1257
1534
  resolveVariants,
1258
1535
  splitStack,