@preprio/toolkit 0.6.0-alpha.0 → 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 (59) hide show
  1. package/README.md +268 -59
  2. package/dist/astro/{chunk-3UVUDDNY.js → chunk-CG2QBHNU.js} +1 -1
  3. package/dist/astro/graphql.cjs +154 -59
  4. package/dist/astro/graphql.d.cts +75 -6
  5. package/dist/astro/graphql.d.ts +75 -6
  6. package/dist/astro/graphql.js +150 -59
  7. package/dist/astro/index.cjs +1 -1
  8. package/dist/astro/index.js +1 -1
  9. package/dist/chunk-3NCEVJJ3.js +10 -0
  10. package/dist/{chunk-BJ2LBLWO.js → chunk-LIDUJWWJ.js} +6 -4
  11. package/dist/chunk-M3RBYJ5J.js +499 -0
  12. package/dist/{chunk-DGZA7KX7.js → chunk-S5RWVMDJ.js} +0 -24
  13. package/dist/{chunk-YR7P4IWE.js → chunk-SF6TN4JR.js} +1 -9
  14. package/dist/chunk-TN6SNAR2.js +26 -0
  15. package/dist/graphql/codegen.cjs +557 -0
  16. package/dist/graphql/codegen.d.cts +23 -0
  17. package/dist/graphql/codegen.d.ts +23 -0
  18. package/dist/graphql/codegen.js +99 -0
  19. package/dist/graphql.cjs +185 -59
  20. package/dist/graphql.d.cts +110 -6
  21. package/dist/graphql.d.ts +110 -6
  22. package/dist/graphql.js +91 -430
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.cts +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +10 -6
  27. package/dist/nextjs/chunk-B4GVZEOL.js +549 -0
  28. package/dist/nextjs/components.cjs +297 -81
  29. package/dist/nextjs/components.d.cts +26 -11
  30. package/dist/nextjs/components.d.ts +26 -11
  31. package/dist/nextjs/components.js +289 -73
  32. package/dist/nextjs/graphql.cjs +952 -315
  33. package/dist/nextjs/graphql.d.cts +225 -83
  34. package/dist/nextjs/graphql.d.ts +225 -83
  35. package/dist/nextjs/graphql.js +634 -314
  36. package/dist/nextjs/index.cjs +116 -54
  37. package/dist/nextjs/index.d.cts +61 -23
  38. package/dist/nextjs/index.d.ts +61 -23
  39. package/dist/nextjs/index.js +97 -299
  40. package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.cts} +1 -1
  41. package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.ts} +1 -1
  42. package/dist/nuxt/{chunk-P6ENNAEE.js → chunk-FMC3DP7N.js} +1 -1
  43. package/dist/nuxt/graphql.cjs +154 -59
  44. package/dist/nuxt/graphql.d.cts +75 -6
  45. package/dist/nuxt/graphql.d.ts +75 -6
  46. package/dist/nuxt/graphql.js +150 -59
  47. package/dist/nuxt/index.cjs +1 -1
  48. package/dist/nuxt/index.js +1 -1
  49. package/dist/react/components.cjs +1 -1
  50. package/dist/react/components.js +1 -1
  51. package/dist/sveltekit/{chunk-FSARVMG5.js → chunk-AJPKBAHO.js} +1 -1
  52. package/dist/sveltekit/graphql.cjs +154 -59
  53. package/dist/sveltekit/graphql.d.cts +75 -6
  54. package/dist/sveltekit/graphql.d.ts +75 -6
  55. package/dist/sveltekit/graphql.js +150 -59
  56. package/dist/sveltekit/index.cjs +1 -1
  57. package/dist/sveltekit/index.js +1 -1
  58. package/package.json +6 -1
  59. package/dist/nextjs/chunk-U4HXENF7.js +0 -231
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name2 in all)
@@ -15,22 +17,109 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/nextjs/graphql.ts
21
31
  var graphql_exports = {};
22
32
  __export(graphql_exports, {
23
- PREPR_DECISIONS_COOKIE: () => PREPR_DECISIONS_COOKIE,
33
+ PREPR_ANY_ITEM_TAG: () => PREPR_ANY_ITEM_TAG,
24
34
  PREPR_DEFAULT_VARIANT: () => PREPR_DEFAULT_VARIANT,
25
35
  PreprError: () => PreprError,
26
36
  PreprStack: () => PreprStack,
27
37
  createPreprClient: () => createPreprClient2,
38
+ createPreprProxy: () => createPreprProxy,
39
+ decisionsFrom: () => decisionsFrom,
28
40
  isPersonalizedResponse: () => isPersonalizedResponse,
41
+ loadPreprSchema: () => loadPreprSchema,
42
+ preprCacheTags: () => preprCacheTags,
43
+ preprModelTag: () => preprModelTag,
29
44
  preprVariantProps: () => preprVariantProps,
30
45
  resolveVariants: () => resolveVariants,
31
- splitStack: () => splitStack
46
+ splitStack: () => splitStack,
47
+ toDecisionQuery: () => toDecisionQuery,
48
+ toVariantsQuery: () => toVariantsQuery
32
49
  });
33
50
  module.exports = __toCommonJS(graphql_exports);
51
+ var nextCache = __toESM(require("next/cache"), 1);
52
+
53
+ // src/core/graphql/cache-tags.ts
54
+ var MAX_TAGS = 128;
55
+ var MAX_TAG_LENGTH = 256;
56
+ var PREPR_ANY_ITEM_TAG = "prepr:any";
57
+ var PREPR_SCHEMA_TAG = "prepr:schema";
58
+ var preprItemTag = (id) => `prepr:${id}`;
59
+ var NOT_ALPHANUMERIC = /[^a-z0-9]/g;
60
+ var preprModelTag = (model) => `prepr:model:${model.toLowerCase().replace(NOT_ALPHANUMERIC, "")}`;
61
+ function preprModelTagsFor(document2, data, schema) {
62
+ const operation = document2.definitions.find(
63
+ (definition) => definition.kind === "OperationDefinition" && definition.operation === "query"
64
+ );
65
+ const root = schema.types[schema.queryType];
66
+ if (!operation || !root) return [];
67
+ const models = (type) => {
68
+ const members = schema.types[type]?.possibleTypes ?? [];
69
+ return members.length > 0 ? members : [type];
70
+ };
71
+ const tags = /* @__PURE__ */ new Set();
72
+ for (const selection of operation.selectionSet.selections) {
73
+ if (selection.kind !== "Field") continue;
74
+ const type = root.fields[selection.name.value]?.type;
75
+ const info = type ? schema.types[type] : void 0;
76
+ if (!type || !info) continue;
77
+ const items = info.fields.items?.type;
78
+ const key = (selection.alias ?? selection.name).value;
79
+ const found = typeof data === "object" && data !== null && data[key] != null;
80
+ const affected = items ? models(items) : found ? [] : models(type);
81
+ for (const model of affected) tags.add(preprModelTag(model));
82
+ }
83
+ return [...tags];
84
+ }
85
+ function preprCacheTags(data) {
86
+ const tags = /* @__PURE__ */ new Set();
87
+ const stack = [data];
88
+ while (stack.length > 0) {
89
+ const value = stack.pop();
90
+ if (typeof value !== "object" || value === null) continue;
91
+ const id = value._id;
92
+ if (typeof id === "string" && id !== "") {
93
+ const tag = preprItemTag(id);
94
+ if (tag.length > MAX_TAG_LENGTH) return [PREPR_ANY_ITEM_TAG];
95
+ tags.add(tag);
96
+ if (tags.size > MAX_TAGS) return [PREPR_ANY_ITEM_TAG];
97
+ }
98
+ for (const child of Object.values(value)) stack.push(child);
99
+ }
100
+ return [...tags];
101
+ }
102
+
103
+ // src/version.ts
104
+ var VERSION = "0.6.0-alpha.10";
105
+
106
+ // src/core/features.ts
107
+ function isEnabled(config) {
108
+ if (typeof config === "boolean") return config;
109
+ return config?.enabled ?? true;
110
+ }
111
+ function resolveFeatures(features) {
112
+ return {
113
+ segments: isEnabled(features?.segments),
114
+ abTesting: isEnabled(features?.abTesting),
115
+ editMode: isEnabled(features?.editMode)
116
+ };
117
+ }
118
+ var ALL_FEATURES_ENABLED = Object.freeze({
119
+ segments: true,
120
+ abTesting: true,
121
+ editMode: true
122
+ });
34
123
 
35
124
  // src/core/server.ts
36
125
  var PreprError = class extends Error {
@@ -176,7 +265,10 @@ function decisionSelectionSet(set, typeName, path, ctx) {
176
265
  path,
177
266
  ctx
178
267
  );
179
- if (pruned) {
268
+ if (!pruned) continue;
269
+ if (fragment.typeCondition.name.value === typeName) {
270
+ selections.push(...pruned.selections);
271
+ } else {
180
272
  selections.push(
181
273
  inlineFragment(fragment.typeCondition.name.value, [
182
274
  ...pruned.selections
@@ -186,13 +278,19 @@ function decisionSelectionSet(set, typeName, path, ctx) {
186
278
  continue;
187
279
  }
188
280
  if (selection.kind === "InlineFragment") {
281
+ const condition = selection.typeCondition?.name.value ?? typeName;
189
282
  const pruned = decisionSelectionSet(
190
283
  selection.selectionSet,
191
- selection.typeCondition?.name.value ?? typeName,
284
+ condition,
192
285
  path,
193
286
  ctx
194
287
  );
195
- if (pruned) selections.push({ ...selection, selectionSet: pruned });
288
+ if (!pruned) continue;
289
+ if (condition === typeName && !selection.directives?.length) {
290
+ selections.push(...pruned.selections);
291
+ } else {
292
+ selections.push({ ...selection, selectionSet: pruned });
293
+ }
196
294
  continue;
197
295
  }
198
296
  if (!selection.selectionSet) continue;
@@ -266,20 +364,58 @@ function toDecisionDocument(document2, schema, groupPaths, options) {
266
364
  const used = /* @__PURE__ */ new Set();
267
365
  collectVariables(pruned, used);
268
366
  collectVariables(operation.directives, used);
367
+ const shared = sharedContextFragment(schema);
269
368
  return {
270
369
  kind: "Document",
271
370
  definitions: [
371
+ ...shared ? [shared.definition] : [],
272
372
  {
273
373
  ...operation,
274
374
  name: operation.name && name(`${operation.name.value}Decision`),
275
375
  variableDefinitions: (operation.variableDefinitions ?? []).filter(
276
376
  (definition) => used.has(definition.variable.name.value)
277
377
  ),
278
- selectionSet: pruned
378
+ selectionSet: shared ? shared.apply(pruned) : pruned
279
379
  }
280
380
  ]
281
381
  };
282
382
  }
383
+ function sharedContextFragment(schema) {
384
+ const contextTypes = new Set(
385
+ Object.values(schema.types).flatMap(
386
+ (type) => type.hasContext ? [type.fields._context.type] : []
387
+ )
388
+ );
389
+ if (contextTypes.size !== 1) return null;
390
+ const [contextType] = contextTypes;
391
+ const fragmentName = "c";
392
+ const definition = {
393
+ kind: "FragmentDefinition",
394
+ name: name(fragmentName),
395
+ typeCondition: { kind: "NamedType", name: name(contextType) },
396
+ selectionSet: selectionSet(
397
+ ["kind", "group_id", "variant_id"].map(
398
+ (field) => ({ kind: "Field", name: name(field) })
399
+ )
400
+ )
401
+ };
402
+ const spread = {
403
+ kind: "FragmentSpread",
404
+ name: name(fragmentName)
405
+ };
406
+ const apply = (set) => selectionSet(
407
+ set.selections.map((selection) => {
408
+ if (selection.kind === "Field" && selection.alias?.value === PREPR_CONTEXT_ALIAS) {
409
+ return { ...selection, selectionSet: selectionSet([spread]) };
410
+ }
411
+ return "selectionSet" in selection && selection.selectionSet ? {
412
+ ...selection,
413
+ selectionSet: apply(selection.selectionSet)
414
+ } : selection;
415
+ })
416
+ );
417
+ return { definition, apply };
418
+ }
283
419
 
284
420
  // src/core/variants/probe-document.ts
285
421
  function probeSelectionSet(set, fragments) {
@@ -566,6 +702,63 @@ function toVariantsDocument(document2, schema) {
566
702
  };
567
703
  }
568
704
 
705
+ // src/core/graphql/document.ts
706
+ var import_printer = require("graphql/language/printer.js");
707
+ var import_stripIgnoredCharacters = require("graphql/utilities/stripIgnoredCharacters.js");
708
+ var printed = /* @__PURE__ */ new WeakMap();
709
+ function isDocumentNode(value) {
710
+ return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
711
+ }
712
+ function invalidDocument(originalError) {
713
+ return new PreprError(
714
+ "Expected a GraphQL query string, TypedDocumentString or DocumentNode",
715
+ "INVALID_DOCUMENT",
716
+ "createPreprClient",
717
+ originalError
718
+ );
719
+ }
720
+ var minified = /* @__PURE__ */ new Map();
721
+ var MINIFIED_CACHE_LIMIT = 500;
722
+ function minify(query) {
723
+ const cached = minified.get(query);
724
+ if (cached !== void 0) return cached;
725
+ let result = query;
726
+ try {
727
+ result = (0, import_stripIgnoredCharacters.stripIgnoredCharacters)(query);
728
+ } catch {
729
+ }
730
+ if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
731
+ return result;
732
+ }
733
+ function resolveQuery(document2) {
734
+ if (typeof document2 === "string") return minify(document2);
735
+ if (document2 instanceof String) return minify(document2.toString());
736
+ if (!isDocumentNode(document2)) throw invalidDocument();
737
+ let query = printed.get(document2);
738
+ if (query === void 0) {
739
+ try {
740
+ query = minify((0, import_printer.print)(document2));
741
+ } catch (error) {
742
+ throw invalidDocument(error instanceof Error ? error : void 0);
743
+ }
744
+ printed.set(document2, query);
745
+ }
746
+ return query;
747
+ }
748
+
749
+ // src/core/variants/queries.ts
750
+ function toVariantsQuery(document2, schema) {
751
+ return resolveQuery(toVariantsDocument(document2, schema));
752
+ }
753
+ function toDecisionQuery(document2, schema, options = {}) {
754
+ const paths = options.from === void 0 ? void 0 : personalizationPaths(options.from);
755
+ if (paths && paths.size === 0) return null;
756
+ const decision = toDecisionDocument(document2, schema, paths, {
757
+ topLevelOnly: !paths && options.topLevelOnly
758
+ });
759
+ return decision ? resolveQuery(decision) : null;
760
+ }
761
+
569
762
  // src/core/graphql/transport.ts
570
763
  var import_stega2 = require("@vercel/stega");
571
764
 
@@ -625,50 +818,6 @@ function stegaClean(text) {
625
818
  return (0, import_stega.vercelStegaClean)(text);
626
819
  }
627
820
 
628
- // src/core/graphql/document.ts
629
- var import_printer = require("graphql/language/printer.js");
630
- var import_stripIgnoredCharacters = require("graphql/utilities/stripIgnoredCharacters.js");
631
- var printed = /* @__PURE__ */ new WeakMap();
632
- function isDocumentNode(value) {
633
- return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
634
- }
635
- function invalidDocument(originalError) {
636
- return new PreprError(
637
- "Expected a GraphQL query string, TypedDocumentString or DocumentNode",
638
- "INVALID_DOCUMENT",
639
- "createPreprClient",
640
- originalError
641
- );
642
- }
643
- var minified = /* @__PURE__ */ new Map();
644
- var MINIFIED_CACHE_LIMIT = 500;
645
- function minify(query) {
646
- const cached = minified.get(query);
647
- if (cached !== void 0) return cached;
648
- let result = query;
649
- try {
650
- result = (0, import_stripIgnoredCharacters.stripIgnoredCharacters)(query);
651
- } catch {
652
- }
653
- if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
654
- return result;
655
- }
656
- function resolveQuery(document2) {
657
- if (typeof document2 === "string") return minify(document2);
658
- if (document2 instanceof String) return minify(document2.toString());
659
- if (!isDocumentNode(document2)) throw invalidDocument();
660
- let query = printed.get(document2);
661
- if (query === void 0) {
662
- try {
663
- query = minify((0, import_printer.print)(document2));
664
- } catch (error) {
665
- throw invalidDocument(error instanceof Error ? error : void 0);
666
- }
667
- printed.set(document2, query);
668
- }
669
- return query;
670
- }
671
-
672
821
  // src/core/graphql/transport.ts
673
822
  var OPERATION_NAME = /\b(?:query|mutation|subscription)\s+([_A-Za-z][_0-9A-Za-z]*)/;
674
823
  var STEGA_RAW = new RegExp(
@@ -847,6 +996,7 @@ function createVaryProbe(run, options) {
847
996
  }
848
997
 
849
998
  // src/core/graphql/client.ts
999
+ var clientSchemas = /* @__PURE__ */ new WeakMap();
850
1000
  function isParseableUrl(url) {
851
1001
  try {
852
1002
  new URL(url);
@@ -855,16 +1005,18 @@ function isParseableUrl(url) {
855
1005
  return false;
856
1006
  }
857
1007
  }
858
- function createPreprClient(options) {
859
- const { url } = options;
1008
+ function assertPreprUrl(url, source) {
860
1009
  validatePreprToken(url);
861
1010
  if (!isParseableUrl(url)) {
862
1011
  throw new PreprError(
863
1012
  "Prepr GraphQL URL is not a valid URL",
864
1013
  "INVALID_TOKEN",
865
- "createPreprClient"
1014
+ source
866
1015
  );
867
1016
  }
1017
+ }
1018
+ function createPreprClient(options) {
1019
+ assertPreprUrl(options.url, "createPreprClient");
868
1020
  const run = createTransport(options);
869
1021
  const probeVary = createVaryProbe(run, options);
870
1022
  let schema;
@@ -881,7 +1033,7 @@ function createPreprClient(options) {
881
1033
  ));
882
1034
  const variantsDocuments = /* @__PURE__ */ new Map();
883
1035
  const decisionDocuments = /* @__PURE__ */ new Map();
884
- return {
1036
+ const client = {
885
1037
  async query(document2, variables, queryOptions = {}) {
886
1038
  const { variants, ...rest } = queryOptions;
887
1039
  if (!variants) return run(document2, variables, rest);
@@ -893,10 +1045,10 @@ function createPreprClient(options) {
893
1045
  return run(derived, variables, rest);
894
1046
  },
895
1047
  async decide(document2, variables, queryOptions = {}) {
896
- const { from, topLevelOnly, ...rest } = queryOptions;
1048
+ const { from, ...rest } = queryOptions;
897
1049
  const paths = from === void 0 ? void 0 : personalizationPaths(from);
898
1050
  if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
899
- const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : topLevelOnly ? "top" : "*";
1051
+ const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : "*";
900
1052
  let byPaths = decisionDocuments.get(document2);
901
1053
  if (!byPaths) {
902
1054
  byPaths = /* @__PURE__ */ new Map();
@@ -904,9 +1056,7 @@ function createPreprClient(options) {
904
1056
  }
905
1057
  let derived = byPaths.get(signature);
906
1058
  if (derived === void 0) {
907
- derived = toDecisionDocument(document2, await loadSchema(), paths, {
908
- topLevelOnly: !paths && topLevelOnly
909
- });
1059
+ derived = toDecisionDocument(document2, await loadSchema(), paths);
910
1060
  byPaths.set(signature, derived);
911
1061
  }
912
1062
  if (!derived) return /* @__PURE__ */ new Map();
@@ -920,28 +1070,91 @@ function createPreprClient(options) {
920
1070
  (vary) => vary === null || SEGMENTS_VARY.test(vary)
921
1071
  )
922
1072
  };
1073
+ clientSchemas.set(client, loadSchema);
1074
+ return client;
1075
+ }
1076
+
1077
+ // src/core/graphql/schema-loader.ts
1078
+ var schemas = /* @__PURE__ */ new Map();
1079
+ async function loadPreprSchema(url, options = {}) {
1080
+ assertPreprUrl(url, "loadPreprSchema");
1081
+ let schema = schemas.get(url);
1082
+ if (!schema) {
1083
+ const run = createTransport({ url, fetch: options.fetch, stega: "none" });
1084
+ schema = run(
1085
+ PREPR_SCHEMA_QUERY
1086
+ ).then((result) => toSchemaInfo(result.data)).catch((error) => {
1087
+ schemas.delete(url);
1088
+ if (error instanceof PreprError) throw error;
1089
+ throw new PreprError(
1090
+ "Prepr schema response is not an introspection result",
1091
+ "INVALID_RESPONSE",
1092
+ "loadPreprSchema",
1093
+ error instanceof Error ? error : void 0
1094
+ );
1095
+ });
1096
+ schemas.set(url, schema);
1097
+ }
1098
+ return schema;
923
1099
  }
924
1100
 
925
1101
  // src/nextjs/decisions.ts
1102
+ var PREPR_DECIDE_HEADER = "Prepr-Decide";
1103
+ var DEFAULT_DECISION = "*";
926
1104
  var PREPR_DECISIONS_COOKIE = "prepr_decisions";
927
- var DECISIONS_MAX_GROUPS = 40;
928
- var SAFE_ID = /^[\w-]+$/;
929
- var SEEN_MAX_PAGES = 1e3;
930
- function parseDecisions(raw) {
931
- const out = /* @__PURE__ */ new Map();
932
- for (const pair of (raw ?? "").split("~")) {
933
- const at = pair.indexOf(":");
934
- const group = pair.slice(0, at);
935
- if (at < 1 || !SAFE_ID.test(group)) continue;
936
- out.set(
937
- group,
938
- pair.slice(at + 1).split("|").filter((variant) => SAFE_ID.test(variant))
939
- );
1105
+ var DECISION_FRESH_MS = 1e4;
1106
+ var DECISION_KEEP_MS = 18e5;
1107
+ var DECISION_KEEP_STATIC_MS = 36e5;
1108
+ var STORED_MAX_ENTRIES = 20;
1109
+ var STORED_MAX_LENGTH = 3e3;
1110
+ function readStoredDecisions(raw) {
1111
+ const stored = /* @__PURE__ */ Object.create(null);
1112
+ if (!raw) return stored;
1113
+ try {
1114
+ const parsed = JSON.parse(raw);
1115
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1116
+ return stored;
1117
+ for (const [key, entry] of Object.entries(parsed)) {
1118
+ if (key.startsWith("/") && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number") {
1119
+ stored[key] = [entry[0], entry[1]];
1120
+ }
1121
+ }
1122
+ } catch {
940
1123
  }
941
- return out;
1124
+ return stored;
1125
+ }
1126
+ function usableDecision(stored, key, now) {
1127
+ const entry = stored[key];
1128
+ if (!entry) return void 0;
1129
+ const [value, at] = entry;
1130
+ const age = now - at;
1131
+ const keep = value === DEFAULT_DECISION ? DECISION_FRESH_MS : value === "" ? DECISION_KEEP_STATIC_MS : DECISION_KEEP_MS;
1132
+ return age >= 0 && age < keep ? value : void 0;
942
1133
  }
1134
+ function writeStoredDecisions(stored, key, value, now) {
1135
+ const kept = usableDecision(stored, key, now);
1136
+ const entries = Object.entries(stored).filter(
1137
+ ([stale]) => stale !== key && usableDecision(stored, stale, now) !== void 0
1138
+ );
1139
+ entries.push([
1140
+ key,
1141
+ value === DEFAULT_DECISION && kept !== void 0 && kept !== DEFAULT_DECISION ? stored[key] : [value, now]
1142
+ ]);
1143
+ entries.sort((a, b) => b[1][1] - a[1][1]);
1144
+ entries.length = Math.min(entries.length, STORED_MAX_ENTRIES);
1145
+ let json = JSON.stringify(Object.fromEntries(entries));
1146
+ while (entries.length > 1 && encodeURIComponent(json).length > STORED_MAX_LENGTH) {
1147
+ entries.pop();
1148
+ json = JSON.stringify(Object.fromEntries(entries));
1149
+ }
1150
+ return json;
1151
+ }
1152
+ var DECISIONS_MAX_GROUPS = 40;
1153
+ var SAFE_ID = /^[\w-]+$/;
943
1154
  function serializeDecisions(decisions) {
944
- return [...decisions].slice(0, DECISIONS_MAX_GROUPS).map(([group, variants]) => `${group}:${variants.join("|")}`).join("~");
1155
+ return [...decisions].filter(([group]) => SAFE_ID.test(group)).slice(0, DECISIONS_MAX_GROUPS).map(
1156
+ ([group, variants]) => `${group}:${variants.filter((v) => SAFE_ID.test(v)).join("|")}`
1157
+ ).join("~");
945
1158
  }
946
1159
  function forwardedHeaders(request, response) {
947
1160
  const overridden = response.headers.get("x-middleware-override-headers");
@@ -953,60 +1166,17 @@ function forwardedHeaders(request, response) {
953
1166
  }
954
1167
  return getPreprHeadersFromHeaders(headers2);
955
1168
  }
956
- async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
1169
+ var preprClientInternals = /* @__PURE__ */ new WeakMap();
1170
+ async function decideView(client, headers2, page, timeoutMs = 1e3) {
1171
+ const init = { signal: AbortSignal.timeout(timeoutMs) };
957
1172
  try {
958
- if (options?.onlyIf && !await options.onlyIf()) return;
959
- if (!await client.hasSegments(document2, variables)) return;
960
- const headers2 = mergePreprHeaders(forwardedHeaders(request, response));
961
- const stored = parseDecisions(
962
- request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
963
- );
964
- const pageKey = `${JSON.stringify(variables ?? null)}`;
965
- let byVariables = seenGroups.get(document2);
966
- if (!byVariables) {
967
- byVariables = /* @__PURE__ */ new Map();
968
- seenGroups.set(document2, byVariables);
969
- }
970
- const known = byVariables.get(pageKey);
971
- const isNewVisitor = headers2.get("Prepr-Customer-Id-Created") === "true";
972
- const contextChanged = isNewVisitor || headers2.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
973
- (key) => key.startsWith("utm_")
974
- );
975
- if (options?.reuse !== false && !contextChanged && known && [...known].every((group) => stored.has(group))) {
976
- return;
977
- }
978
- const decided = await client.decide(document2, variables, {
979
- topLevelOnly: true,
980
- headers: headers2,
981
- init: { signal: AbortSignal.timeout(options?.timeoutMs ?? 1e3) }
982
- });
983
- if (!known && byVariables.size >= SEEN_MAX_PAGES) {
984
- byVariables.delete(byVariables.keys().next().value);
985
- }
986
- const groups = known ?? /* @__PURE__ */ new Set();
987
- for (const group of decided.keys()) groups.add(group);
988
- byVariables.set(pageKey, groups);
989
- const next = /* @__PURE__ */ new Map();
990
- for (const [group, variants] of decided) {
991
- if (SAFE_ID.test(group)) {
992
- next.set(
993
- group,
994
- variants.filter((v) => SAFE_ID.test(v))
995
- );
996
- }
997
- }
998
- for (const [group, variants] of stored) {
999
- if (!next.has(group)) next.set(group, variants);
1000
- }
1001
- if (next.size === 0) return;
1002
- response.cookies.set(PREPR_DECISIONS_COOKIE, serializeDecisions(next), {
1003
- path: "/",
1004
- sameSite: "lax",
1005
- // Read by the inline switch script before first paint.
1006
- httpOnly: false
1007
- });
1173
+ const decided = page.decision ? decisionsFrom(
1174
+ (await client.query(page.decision, page.variables, { headers: headers2, init })).data
1175
+ ) : await client.decide(page.document, page.variables, { headers: headers2, init });
1176
+ return serializeDecisions(decided);
1008
1177
  } catch (error) {
1009
1178
  if (isFrameworkSignal(error)) throw error;
1179
+ return DEFAULT_DECISION;
1010
1180
  }
1011
1181
  }
1012
1182
 
@@ -1020,227 +1190,668 @@ async function getPreprHeaders() {
1020
1190
  return getPreprHeadersFromHeaders(h);
1021
1191
  }
1022
1192
 
1023
- // src/nextjs/stack.ts
1024
- var import_react = require("react");
1193
+ // src/nextjs/proxy.ts
1194
+ var import_server9 = require("next/server");
1025
1195
 
1026
- // src/nextjs/ab-switch.ts
1027
- function applyPreprAbBucket() {
1028
- const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
1029
- const bucket = match ? match[1] : "A";
1030
- const state = window;
1031
- if (state.__preprAbBucket === bucket) return;
1032
- state.__preprAbBucket = bucket;
1033
- const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
1034
- try {
1035
- if (!state.__preprAbSheet) {
1036
- const sheet = new CSSStyleSheet();
1037
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
1038
- state.__preprAbSheet = sheet;
1196
+ // src/nextjs/draft.ts
1197
+ var import_headers2 = require("next/headers");
1198
+ var import_navigation = require("next/navigation");
1199
+ var import_server7 = require("next/server");
1200
+ var DRAFT_COOKIE = "__prerender_bypass";
1201
+ var RETURN_PARAM = "prepr_draft";
1202
+ var DEFAULT_ROUTE = "/api/prepr/draft";
1203
+ function preprDraftRedirect(request, options) {
1204
+ if (!options.preview) return null;
1205
+ if (request.method !== "GET" && request.method !== "HEAD") return null;
1206
+ const url = request.nextUrl;
1207
+ if (url.pathname === (options.route ?? DEFAULT_ROUTE)) return null;
1208
+ if (request.cookies.has(DRAFT_COOKIE)) {
1209
+ if (!url.searchParams.has(RETURN_PARAM)) return null;
1210
+ const clean = url.clone();
1211
+ clean.searchParams.delete(RETURN_PARAM);
1212
+ return import_server7.NextResponse.redirect(clean, 307);
1213
+ }
1214
+ if (url.searchParams.has(RETURN_PARAM)) return null;
1215
+ const target = new URL(options.route ?? DEFAULT_ROUTE, request.url);
1216
+ target.searchParams.set("to", url.pathname + url.search);
1217
+ return import_server7.NextResponse.redirect(target, 307);
1218
+ }
1219
+
1220
+ // src/nextjs/middleware.ts
1221
+ var import_server8 = require("next/server");
1222
+
1223
+ // src/core/constants.ts
1224
+ var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
1225
+ var COOKIE_SEGMENT = "Prepr-Segments";
1226
+ var COOKIE_VARIANT = "Prepr-ABtesting";
1227
+ var COOKIE_UID = "__prepr_uid";
1228
+ var COOKIE_HUBSPOT = "hubspotutk";
1229
+ var PARAM_SEGMENT = "prepr_preview_segment";
1230
+ var PARAM_VARIANT = "prepr_preview_ab";
1231
+ var PARAM_HIDE_BAR = "prepr_hide_bar";
1232
+ var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
1233
+
1234
+ // src/core/middleware.ts
1235
+ var UTM_PARAMS = [
1236
+ "utm_source",
1237
+ "utm_medium",
1238
+ "utm_term",
1239
+ "utm_content",
1240
+ "utm_campaign"
1241
+ ];
1242
+ var MAX_HEADER_VALUE_LENGTH = 2048;
1243
+ function sanitizeHeaderValue(value) {
1244
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
1245
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
1246
+ }
1247
+ function parseCookies(request) {
1248
+ const cookies2 = /* @__PURE__ */ new Map();
1249
+ const header = request.headers.get("cookie");
1250
+ if (!header) return cookies2;
1251
+ for (const part of header.split(";")) {
1252
+ const eq = part.indexOf("=");
1253
+ if (eq === -1) continue;
1254
+ const name2 = part.slice(0, eq).trim();
1255
+ const value = part.slice(eq + 1).trim();
1256
+ if (!name2) continue;
1257
+ try {
1258
+ cookies2.set(name2, decodeURIComponent(value));
1259
+ } catch {
1260
+ cookies2.set(name2, value);
1039
1261
  }
1040
- state.__preprAbSheet.replaceSync(css);
1041
- } catch {
1042
- if (!state.__preprAbSheet) {
1043
- state.__preprAbSheet = document.createElement("style");
1044
- document.head.appendChild(state.__preprAbSheet);
1262
+ }
1263
+ return cookies2;
1264
+ }
1265
+ function processFrameworkRequest(request, opts, framework) {
1266
+ const requestHeaders = new Headers(request.headers);
1267
+ const responseCookies = [];
1268
+ const inboundPreprHeaders = [];
1269
+ requestHeaders.forEach((_value, key) => {
1270
+ if (key.toLowerCase().startsWith("prepr-")) {
1271
+ inboundPreprHeaders.push(key);
1272
+ }
1273
+ });
1274
+ for (const key of inboundPreprHeaders) {
1275
+ requestHeaders.delete(key);
1276
+ }
1277
+ const requestCookies = parseCookies(request);
1278
+ const url = new URL(request.url);
1279
+ const searchParams = url.searchParams;
1280
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
1281
+ const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
1282
+ const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
1283
+ for (const key of UTM_PARAMS) {
1284
+ const value = searchParams.get(key);
1285
+ if (value !== null) {
1286
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
1045
1287
  }
1046
- state.__preprAbSheet.textContent = css;
1047
1288
  }
1289
+ const referer = request.headers.get("referer");
1290
+ if (referer) {
1291
+ requestHeaders.set(
1292
+ "Prepr-Context-initial_referral",
1293
+ sanitizeHeaderValue(referer)
1294
+ );
1295
+ }
1296
+ const userAgent = request.headers.get("user-agent");
1297
+ if (userAgent) {
1298
+ requestHeaders.set("User-Agent", sanitizeHeaderValue(userAgent));
1299
+ }
1300
+ const packageParts = [`@preprio/toolkit@${opts?.version ?? VERSION}`];
1301
+ if (framework) packageParts.push(`framework=${framework}`);
1302
+ packageParts.push(`preview=${opts?.preview ? "true" : "false"}`);
1303
+ requestHeaders.set("Prepr-Package", packageParts.join("; "));
1304
+ const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
1305
+ if (ip) {
1306
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
1307
+ }
1308
+ const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
1309
+ if (hutkCookie) {
1310
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
1311
+ }
1312
+ let uid = sanitizeHeaderValue(requestCookies.get(COOKIE_UID) ?? "");
1313
+ if (!uid) {
1314
+ uid = crypto.randomUUID();
1315
+ responseCookies.push({
1316
+ name: COOKIE_UID,
1317
+ value: uid,
1318
+ maxAge: ONE_YEAR_SECONDS,
1319
+ path: "/"
1320
+ });
1321
+ requestHeaders.set("Prepr-Customer-Id-Created", "true");
1322
+ }
1323
+ requestHeaders.set("Prepr-Customer-Id", uid);
1324
+ if (!opts?.preview) {
1325
+ return { requestHeaders, responseCookies };
1326
+ }
1327
+ const features = resolveFeatures(opts?.features);
1328
+ const hasPreviewParams = features.segments && searchParams.has(PARAM_SEGMENT) || features.abTesting && searchParams.has(PARAM_VARIANT);
1329
+ if (!hasPreviewParams) {
1330
+ const previewModeCookie = requestCookies.get(COOKIE_PREVIEW_MODE);
1331
+ const previewModeEnabled = previewModeCookie !== "false";
1332
+ if (!previewModeEnabled) {
1333
+ return { requestHeaders, responseCookies };
1334
+ }
1335
+ }
1336
+ requestHeaders.set("Prepr-Preview-Bar", "true");
1337
+ const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
1338
+ if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
1339
+ responseCookies.push({
1340
+ name: COOKIE_PREVIEW_MODE,
1341
+ value: "true",
1342
+ maxAge: ONE_YEAR_SECONDS,
1343
+ path: "/",
1344
+ ...crossSite
1345
+ });
1346
+ }
1347
+ if (!isLivePreview) {
1348
+ const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
1349
+ if (segmentCookie) {
1350
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
1351
+ }
1352
+ const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
1353
+ if (abCookie) {
1354
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
1355
+ }
1356
+ }
1357
+ const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
1358
+ if (previewAb !== null) {
1359
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
1360
+ if (!isLivePreview) {
1361
+ responseCookies.push({
1362
+ name: COOKIE_VARIANT,
1363
+ value: previewAb,
1364
+ maxAge: ONE_YEAR_SECONDS,
1365
+ path: "/",
1366
+ ...crossSite
1367
+ });
1368
+ }
1369
+ }
1370
+ const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
1371
+ if (previewSegment !== null) {
1372
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
1373
+ if (!isLivePreview) {
1374
+ responseCookies.push({
1375
+ name: COOKIE_SEGMENT,
1376
+ value: previewSegment,
1377
+ maxAge: ONE_YEAR_SECONDS,
1378
+ path: "/",
1379
+ ...crossSite
1380
+ });
1381
+ }
1382
+ }
1383
+ if (requestHeaders.has("Prepr-Segments")) {
1384
+ requestHeaders.delete("Prepr-Customer-Id");
1385
+ requestHeaders.delete("Prepr-Customer-Id-Created");
1386
+ }
1387
+ return { requestHeaders, responseCookies };
1048
1388
  }
1049
- function applyPreprDecisions() {
1050
- const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
1051
- let raw;
1052
- try {
1053
- raw = match ? decodeURIComponent(match[1]) : "";
1054
- } catch {
1055
- raw = "";
1389
+
1390
+ // src/nextjs/middleware.ts
1391
+ function createPreprMiddleware(request, responseOrOptions, maybeOptions) {
1392
+ let chainedResponse;
1393
+ let options;
1394
+ if (responseOrOptions instanceof import_server8.NextResponse) {
1395
+ chainedResponse = responseOrOptions;
1396
+ options = maybeOptions;
1397
+ } else {
1398
+ options = responseOrOptions;
1056
1399
  }
1057
- const state = window;
1058
- if (state.__preprDecisions === raw) return;
1059
- state.__preprDecisions = raw;
1060
- const id = /^[\w-]+$/;
1061
- let css = "";
1062
- raw.split("~").forEach((pair) => {
1063
- const at = pair.indexOf(":");
1064
- const group = pair.slice(0, at);
1065
- if (at < 1 || !id.test(group)) return;
1066
- const selector = '[data-prepr-group="' + group + '"]';
1067
- css += selector + "{display:none!important}";
1068
- pair.slice(at + 1).split("|").forEach((variant) => {
1069
- if (id.test(variant)) {
1070
- css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
1400
+ const { requestHeaders, responseCookies } = processFrameworkRequest(
1401
+ request,
1402
+ options,
1403
+ "nextjs"
1404
+ );
1405
+ const response = chainedResponse ?? import_server8.NextResponse.next({ request: { headers: requestHeaders } });
1406
+ if (chainedResponse) {
1407
+ const existing = response.headers.get("x-middleware-override-headers");
1408
+ const names = new Set(
1409
+ existing ? existing.split(",").map((name2) => name2.trim()).filter(Boolean) : []
1410
+ );
1411
+ requestHeaders.forEach((value, key) => {
1412
+ names.add(key);
1413
+ response.headers.set(`x-middleware-request-${key}`, value);
1414
+ });
1415
+ response.headers.set("x-middleware-override-headers", [...names].join(","));
1416
+ }
1417
+ for (const cookie of responseCookies) {
1418
+ response.cookies.set(cookie.name, cookie.value, {
1419
+ maxAge: cookie.maxAge,
1420
+ path: cookie.path,
1421
+ // Lower-cased: Next's cookie API expects 'none' | 'lax' | 'strict'.
1422
+ ...cookie.sameSite ? {
1423
+ sameSite: cookie.sameSite.toLowerCase()
1424
+ } : {},
1425
+ ...cookie.secure ? { secure: true } : {}
1426
+ });
1427
+ }
1428
+ return response;
1429
+ }
1430
+
1431
+ // src/nextjs/proxy.ts
1432
+ function isRouterFetch(request) {
1433
+ return request.headers.has("rsc") || request.headers.has("next-url") || request.headers.get("sec-fetch-dest") === "empty";
1434
+ }
1435
+ var STATIC_PAGE_MS = 3e5;
1436
+ var STATIC_PAGES_MAX = 1e3;
1437
+ var staticPages = /* @__PURE__ */ new Map();
1438
+ function isPagePath(pathname) {
1439
+ return !pathname.startsWith("/_next/") && !pathname.startsWith("/api/") && !/\.[^/]*$/.test(pathname);
1440
+ }
1441
+ function createPreprProxy(options) {
1442
+ const internals = preprClientInternals.get(options.client);
1443
+ if (!internals) {
1444
+ throw new PreprError(
1445
+ "createPreprProxy needs a client made by createPreprClient from @preprio/toolkit/nextjs/graphql",
1446
+ "CONTEXT_ERROR",
1447
+ "createPreprProxy"
1448
+ );
1449
+ }
1450
+ const { client } = internals;
1451
+ const preview = options.preview ?? false;
1452
+ const allowed = async (request) => {
1453
+ if (!options.onlyIf) return true;
1454
+ try {
1455
+ return await options.onlyIf(request);
1456
+ } catch {
1457
+ return false;
1458
+ }
1459
+ };
1460
+ const lookup = (target) => {
1461
+ try {
1462
+ return options.page(target) ?? null;
1463
+ } catch {
1464
+ return null;
1465
+ }
1466
+ };
1467
+ async function answerDecision(request, response, target) {
1468
+ let decision = DEFAULT_DECISION;
1469
+ const site = request.headers.get("sec-fetch-site");
1470
+ if (target && request.method === "GET" && (site === null || site === "same-origin") && await allowed(request)) {
1471
+ decision = await decideView(
1472
+ client,
1473
+ mergePreprHeaders(forwardedHeaders(request, response)),
1474
+ target,
1475
+ options.timeoutMs
1476
+ );
1477
+ }
1478
+ const answer = new import_server9.NextResponse(decision, {
1479
+ headers: {
1480
+ "Cache-Control": "private, no-store",
1481
+ "Content-Type": "text/plain; charset=utf-8"
1482
+ }
1483
+ });
1484
+ for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
1485
+ return answer;
1486
+ }
1487
+ async function decidePageLoad(request, response, target) {
1488
+ const { basePath, pathname, search } = request.nextUrl;
1489
+ const key = basePath + pathname + search;
1490
+ const now = Date.now();
1491
+ if ((staticPages.get(key) ?? 0) > now) return response;
1492
+ const stored = readStoredDecisions(
1493
+ request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1494
+ );
1495
+ if (!preview && usableDecision(stored, key, now) !== void 0)
1496
+ return response;
1497
+ if (!await allowed(request)) return response;
1498
+ const decision = await decideView(
1499
+ client,
1500
+ mergePreprHeaders(forwardedHeaders(request, response)),
1501
+ target,
1502
+ options.timeoutMs
1503
+ );
1504
+ if (decision === "") {
1505
+ if (staticPages.size >= STATIC_PAGES_MAX) staticPages.clear();
1506
+ staticPages.set(key, now + STATIC_PAGE_MS);
1507
+ return response;
1508
+ }
1509
+ response.cookies.set(
1510
+ PREPR_DECISIONS_COOKIE,
1511
+ writeStoredDecisions(stored, key, decision, now),
1512
+ {
1513
+ path: "/",
1514
+ maxAge: 3600,
1515
+ sameSite: "lax",
1516
+ secure: request.nextUrl.protocol === "https:"
1071
1517
  }
1518
+ );
1519
+ return response;
1520
+ }
1521
+ return async (request) => {
1522
+ const rendersPage = (request.method === "GET" || request.method === "HEAD") && isPagePath(request.nextUrl.pathname);
1523
+ const target = rendersPage ? lookup(request.nextUrl) : null;
1524
+ const decisionRequest = request.headers.has(PREPR_DECIDE_HEADER);
1525
+ if (target && preview && !decisionRequest) {
1526
+ const draft = preprDraftRedirect(request, { preview });
1527
+ if (draft) return draft;
1528
+ }
1529
+ const response = createPreprMiddleware(request, {
1530
+ preview,
1531
+ features: options.features
1072
1532
  });
1533
+ if (decisionRequest) return answerDecision(request, response, target);
1534
+ if (target && request.method === "GET" && !isRouterFetch(request))
1535
+ return decidePageLoad(request, response, target);
1536
+ return response;
1537
+ };
1538
+ }
1539
+
1540
+ // src/nextjs/stack.ts
1541
+ var import_react = require("react");
1542
+
1543
+ // src/nextjs/decision-switch.ts
1544
+ function applyPreprDecisions(decision, options = {}) {
1545
+ const state = window;
1546
+ const key = location.pathname + location.search;
1547
+ const previous = state.__preprDecision;
1548
+ let value = decision;
1549
+ if (value !== void 0 && value !== "*" && options.refresh && previous && previous.key === key && previous.value !== "*") {
1550
+ const pairs = (from) => {
1551
+ const out = {};
1552
+ from.split("~").forEach((pair) => {
1553
+ const colon = pair.indexOf(":");
1554
+ if (colon > 0) out[pair.slice(0, colon)] = pair;
1555
+ });
1556
+ return out;
1557
+ };
1558
+ const before = pairs(previous.value);
1559
+ const after = pairs(value);
1560
+ document.querySelectorAll("[data-prepr-v][data-prepr-show]").forEach((element) => {
1561
+ const range = document.createRange();
1562
+ range.selectNodeContents(element);
1563
+ if (range.getBoundingClientRect().top >= window.innerHeight) return;
1564
+ (element.getAttribute("data-prepr-v") || "").split(" ").forEach((token) => {
1565
+ const group = token.slice(0, token.indexOf(":"));
1566
+ if (before[group]) after[group] = before[group];
1567
+ });
1568
+ });
1569
+ value = Object.keys(after).map((group) => after[group]).join("~");
1570
+ }
1571
+ if (value !== void 0) state.__preprDecision = { key, value };
1572
+ const current = state.__preprDecision;
1573
+ if (!current || current.key !== key) return false;
1574
+ document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
1575
+ marker.setAttribute("data-prepr-decided", "");
1073
1576
  });
1074
- try {
1075
- if (!state.__preprDecisionSheet) {
1076
- const sheet = new CSSStyleSheet();
1077
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
1078
- state.__preprDecisionSheet = sheet;
1577
+ const defaults = current.value === "*";
1578
+ const chosen = {};
1579
+ current.value.split("~").forEach((pair) => {
1580
+ const colon = pair.indexOf(":");
1581
+ if (colon > 0)
1582
+ chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
1583
+ });
1584
+ document.querySelectorAll("[data-prepr-v]").forEach((element) => {
1585
+ const nested = element.hasAttribute("data-prepr-nested");
1586
+ const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
1587
+ const colon = token.indexOf(":");
1588
+ const variant = token.slice(colon + 1);
1589
+ const variants = chosen[token.slice(0, colon)];
1590
+ return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
1591
+ });
1592
+ if (show) element.setAttribute("data-prepr-show", "");
1593
+ else element.removeAttribute("data-prepr-show");
1594
+ });
1595
+ return true;
1596
+ }
1597
+ function requestPreprDecision(key, options = {}) {
1598
+ const fresh = 1e4;
1599
+ const keep = 18e5;
1600
+ const keepStatic = 36e5;
1601
+ const state = window;
1602
+ const asking = state.__preprAsking = state.__preprAsking || {};
1603
+ const read = () => {
1604
+ const all = {};
1605
+ try {
1606
+ const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
1607
+ const parsed = match ? JSON.parse(decodeURIComponent(match[1])) : null;
1608
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
1609
+ Object.keys(parsed).forEach((stored) => {
1610
+ const entry = parsed[stored];
1611
+ if (stored.charAt(0) === "/" && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number")
1612
+ all[stored] = [entry[0], entry[1]];
1613
+ });
1614
+ } catch {
1079
1615
  }
1080
- state.__preprDecisionSheet.replaceSync(css);
1081
- } catch {
1082
- if (!state.__preprDecisionSheet) {
1083
- state.__preprDecisionSheet = document.createElement("style");
1084
- document.head.appendChild(state.__preprDecisionSheet);
1616
+ return all;
1617
+ };
1618
+ const usable = (entry, now) => {
1619
+ if (!entry) return false;
1620
+ const age = now - entry[1];
1621
+ return age >= 0 && age < (entry[0] === "*" ? fresh : entry[0] === "" ? keepStatic : keep);
1622
+ };
1623
+ const store = (answer) => {
1624
+ const all = read();
1625
+ const now = Date.now();
1626
+ const kept = all[key];
1627
+ const entries = Object.keys(all).filter((stored) => stored !== key && usable(all[stored], now)).map((stored) => [stored, all[stored]]);
1628
+ entries.push([
1629
+ key,
1630
+ answer === "*" && kept && usable(kept, now) && kept[0] !== "*" ? kept : [answer, now]
1631
+ ]);
1632
+ entries.sort((a, b) => b[1][1] - a[1][1]);
1633
+ entries.length = Math.min(entries.length, 20);
1634
+ const encode = () => {
1635
+ const out = {};
1636
+ entries.forEach((entry) => {
1637
+ out[entry[0]] = entry[1];
1638
+ });
1639
+ return encodeURIComponent(JSON.stringify(out));
1640
+ };
1641
+ let value = encode();
1642
+ while (entries.length > 1 && value.length > 3e3) {
1643
+ entries.pop();
1644
+ value = encode();
1085
1645
  }
1086
- state.__preprDecisionSheet.textContent = css;
1646
+ document.cookie = "prepr_decisions=" + value + "; path=/; max-age=3600; samesite=lax" + (location.protocol === "https:" ? "; secure" : "");
1647
+ };
1648
+ const ask = () => {
1649
+ if (!asking[key]) {
1650
+ asking[key] = fetch(key, {
1651
+ headers: { "Prepr-Decide": "1" },
1652
+ cache: "no-store",
1653
+ credentials: "same-origin",
1654
+ // The proxy gives up on Prepr after its own `timeoutMs`; this only
1655
+ // covers a proxy that never answers.
1656
+ signal: AbortSignal.timeout ? AbortSignal.timeout(6e3) : void 0
1657
+ }).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((answer) => {
1658
+ delete asking[key];
1659
+ try {
1660
+ store(answer);
1661
+ } catch {
1662
+ }
1663
+ return answer;
1664
+ });
1665
+ }
1666
+ return asking[key];
1667
+ };
1668
+ const last = read()[key];
1669
+ if (last && !(last[0] === "" && options.personalized) && usable(last, Date.now())) {
1670
+ const value = last[0];
1671
+ if (value !== "" && Date.now() - last[1] >= fresh) {
1672
+ ask().then((answer) => {
1673
+ if (answer !== "*" && answer !== value && options.onChange)
1674
+ options.onChange(answer);
1675
+ });
1676
+ }
1677
+ return { result: value, value: Promise.resolve(value) };
1087
1678
  }
1679
+ const pending = {
1680
+ value: ask()
1681
+ };
1682
+ pending.value.then((answer) => {
1683
+ pending.result = answer;
1684
+ });
1685
+ return pending;
1686
+ }
1687
+ function startPreprDecision() {
1688
+ const state = window;
1689
+ const key = location.pathname + location.search;
1690
+ const apply = (answer, refresh) => {
1691
+ if (location.pathname + location.search === key)
1692
+ state.__preprApply(answer, { refresh });
1693
+ };
1694
+ const pending = state.__preprRequest(key, {
1695
+ onChange: (answer) => apply(answer, true),
1696
+ personalized: true
1697
+ });
1698
+ if (pending.result !== void 0) apply(pending.result);
1699
+ else pending.value.then((answer) => apply(answer));
1088
1700
  }
1089
1701
 
1090
1702
  // src/nextjs/stack.ts
1091
1703
  var import_components = require("./components.cjs");
1092
- var AB_ATTRIBUTE = "data-prepr-ab";
1093
- var AB_BUCKETS = ["A", "B"];
1094
- var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
1095
- var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
1096
- var GROUP_ATTRIBUTE = "data-prepr-group";
1097
- var VARIANT_ATTRIBUTE = "data-prepr-variant";
1704
+ var VARIANT_ATTRIBUTE = "data-prepr-v";
1098
1705
  var DEFAULT_ATTRIBUTE = "data-prepr-default";
1099
- var GROUP_BASE_CSS = `[${GROUP_ATTRIBUTE}]{display:contents}[${GROUP_ATTRIBUTE}]:not([${DEFAULT_ATTRIBUTE}]){display:none}`;
1706
+ var NESTED_ATTRIBUTE = "data-prepr-nested";
1707
+ 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}`;
1708
+ var NOSCRIPT_HTML = `<style>[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents!important;visibility:visible!important}</style>`;
1709
+ var DECISION_SCRIPT = `window.__preprApply=${applyPreprDecisions.toString()};window.__preprRequest=${requestPreprDecision.toString()};(${startPreprDecision.toString()})();`;
1710
+ var APPLY_SCRIPT = "__preprApply()";
1100
1711
  function variantKeysOf(items) {
1101
1712
  return items.flatMap((item) => {
1102
- const key = preprVariantProps(item)["data-prepr-variant-key"];
1713
+ const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
1103
1714
  return key ? [key] : [];
1104
1715
  });
1105
1716
  }
1106
- function renderBuckets(items, render, options) {
1107
- const {
1108
- bucket,
1109
- decisions,
1110
- abTest,
1111
- index,
1112
- track,
1113
- placeholder = false
1114
- } = options;
1115
- const renderVariant = (variant, key) => {
1116
- const resolved = resolveVariants(items, { decisions, bucket: variant });
1117
- const content = render(resolved, { index, placeholder });
1118
- const variantKeys = track ? variantKeysOf(resolved) : [];
1119
- const ab = abTest && !bucket ? variant : void 0;
1120
- if (variantKeys.length > 0) {
1121
- return (0, import_react.createElement)(
1122
- import_components.PreprVariantTracking,
1123
- { key, variantKeys, ab },
1124
- content
1125
- );
1717
+ function nestedKeyOf(value) {
1718
+ if (!value || typeof value !== "object") return void 0;
1719
+ for (const child of Object.values(value)) {
1720
+ const items = Array.isArray(child) ? child : [child];
1721
+ for (const item of items) {
1722
+ const key = contextOf(item)?.variant_key ?? (item && nestedKeyOf(item));
1723
+ if (key) return key;
1126
1724
  }
1127
- if (ab) return (0, import_react.createElement)("div", { key, [AB_ATTRIBUTE]: ab }, content);
1128
- return content;
1725
+ }
1726
+ return void 0;
1727
+ }
1728
+ function renderItems(items, render, options) {
1729
+ const resolved = resolveVariants(items, {
1730
+ decisions: options.decisions,
1731
+ bucket: options.bucket
1732
+ });
1733
+ const content = render(resolved, {
1734
+ index: options.index,
1735
+ defaultVariant: options.defaultVariant
1736
+ });
1737
+ const variantKeys = options.track ? variantKeysOf(resolved) : [];
1738
+ return variantKeys.length > 0 ? (0, import_react.createElement)(import_components.PreprVariantTracking, { variantKeys }, content) : content;
1739
+ }
1740
+ function decidedGroups(items, bucket) {
1741
+ const groups = /* @__PURE__ */ new Map();
1742
+ const walk = (value) => {
1743
+ if (Array.isArray(value)) return value.forEach(walk);
1744
+ if (!value || typeof value !== "object") return;
1745
+ const ctx = contextOf(value);
1746
+ const decided = ctx?.kind === "PERSONALIZATION" || ctx?.kind === "AB_TEST" && !bucket;
1747
+ if (decided && ctx.variant_id) {
1748
+ const group = groups.get(ctx.group_id) ?? {
1749
+ variants: /* @__PURE__ */ new Set(),
1750
+ fallback: ctx.kind === "AB_TEST" ? "A" : PREPR_DEFAULT_VARIANT
1751
+ };
1752
+ group.variants.add(ctx.variant_id);
1753
+ groups.set(ctx.group_id, group);
1754
+ }
1755
+ Object.values(value).forEach(walk);
1129
1756
  };
1130
- if (!abTest || bucket) return renderVariant(bucket);
1131
- return AB_BUCKETS.map((variant) => renderVariant(variant, variant));
1132
- }
1133
- function renderAllVariants(block, render, options) {
1134
- const group = block.key.slice("group:".length);
1135
- const variants = [
1136
- ...new Set(
1137
- block.items.flatMap((item) => {
1138
- const ctx = item[PREPR_CONTEXT_ALIAS];
1139
- return ctx?.variant_id ? [ctx.variant_id] : [];
1140
- })
1141
- )
1142
- ];
1143
- return variants.map(
1144
- (variant) => (0, import_react.createElement)(
1757
+ walk(items);
1758
+ return groups;
1759
+ }
1760
+ function renderVariants(block, groups, render, options) {
1761
+ const top = block.key.startsWith("group:") ? block.key.slice("group:".length) : void 0;
1762
+ const nested = !(top !== void 0 && groups.size === 1);
1763
+ const choices = [...groups].map(([group, { variants, fallback }]) => {
1764
+ const ids = nested ? [fallback, ...variants] : [...variants];
1765
+ return [...new Set(ids)].map((variant) => [group, variant]);
1766
+ });
1767
+ const combinations = choices.reduce(
1768
+ (all, pairs) => all.flatMap((combo) => pairs.map((p) => [...combo, p])),
1769
+ [[]]
1770
+ );
1771
+ return combinations.map((combination) => {
1772
+ const pairs = combination.map(([group, variant]) => `${group}:${variant}`);
1773
+ const isDefault = combination.every(
1774
+ ([group, variant]) => variant === groups.get(group).fallback
1775
+ );
1776
+ return (0, import_react.createElement)(
1145
1777
  "div",
1146
1778
  {
1147
- key: variant,
1148
- [GROUP_ATTRIBUTE]: group,
1149
- [VARIANT_ATTRIBUTE]: variant,
1150
- ...variant === PREPR_DEFAULT_VARIANT ? { [DEFAULT_ATTRIBUTE]: "" } : {}
1779
+ key: pairs.join(" "),
1780
+ [VARIANT_ATTRIBUTE]: pairs.join(" "),
1781
+ ...isDefault ? { [DEFAULT_ATTRIBUTE]: "" } : {},
1782
+ ...nested ? { [NESTED_ATTRIBUTE]: "" } : {},
1783
+ // The decision script marks the shown wrapper before hydration.
1784
+ suppressHydrationWarning: true
1151
1785
  },
1152
- renderBuckets(block.items, render, {
1786
+ renderItems(block.items, render, {
1153
1787
  bucket: options.bucket,
1154
- decisions: /* @__PURE__ */ new Map([[group, [variant]]]),
1155
- abTest: false,
1788
+ decisions: new Map(combination.map(([g, v]) => [g, [v]])),
1156
1789
  index: block.index,
1157
- track: options.track
1790
+ track: options.track,
1791
+ defaultVariant: isDefault
1158
1792
  })
1159
- )
1160
- );
1161
- }
1162
- async function PersonalizedBlock({
1163
- items,
1164
- decisions,
1165
- bucket,
1166
- abTest,
1167
- index,
1168
- render,
1169
- track
1170
- }) {
1171
- const decided = await decisions.catch((error) => {
1172
- if (isFrameworkSignal(error)) throw error;
1173
- return void 0;
1174
- });
1175
- return renderBuckets(items, render, {
1176
- bucket,
1177
- decisions: decided,
1178
- abTest,
1179
- index,
1180
- track
1793
+ );
1181
1794
  });
1182
1795
  }
1183
1796
  function PreprStack({
1184
1797
  items,
1185
- decisions,
1186
1798
  render,
1187
1799
  bucket,
1188
1800
  nonce,
1189
- trackVariants = false,
1190
- aboveTheFold = 0
1801
+ trackVariants = false
1191
1802
  }) {
1192
- decisions.catch(() => void 0);
1193
- const blocks = splitStack(items);
1194
- const decidedInProxy = (block) => block.index < aboveTheFold && block.personalized && !block.abTest && block.key.startsWith("group:");
1195
- const hasDecided = blocks.some(decidedInProxy);
1196
- const needsSwitch = !bucket && blocks.some((block) => block.abTest) || hasDecided;
1803
+ const blocks = splitStack(items).map((block) => ({
1804
+ ...block,
1805
+ groups: decidedGroups(block.items, bucket)
1806
+ }));
1807
+ const varies = blocks.some((block) => block.groups.size > 0);
1197
1808
  return (0, import_react.createElement)(
1198
1809
  import_react.Fragment,
1199
1810
  null,
1200
- needsSwitch && (0, import_react.createElement)(
1811
+ varies && (0, import_react.createElement)(
1201
1812
  import_react.Fragment,
1202
- { key: "prepr-ab" },
1813
+ { key: "prepr-switch" },
1203
1814
  (0, import_react.createElement)("style", {
1204
1815
  nonce,
1205
- dangerouslySetInnerHTML: {
1206
- __html: AB_BASE_CSS + (hasDecided ? GROUP_BASE_CSS : "")
1207
- }
1816
+ dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
1817
+ }),
1818
+ (0, import_react.createElement)("noscript", {
1819
+ dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
1208
1820
  }),
1209
1821
  (0, import_react.createElement)("script", {
1210
1822
  nonce,
1211
- dangerouslySetInnerHTML: { __html: AB_SWITCH_SCRIPT }
1823
+ dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
1212
1824
  }),
1213
- (0, import_react.createElement)(import_components.PreprAbSwitch)
1825
+ (0, import_react.createElement)(import_components.PreprDecisionApplier),
1826
+ // Marked decided by the decision switch; never laid out.
1827
+ (0, import_react.createElement)("template", {
1828
+ "data-prepr-decision": "",
1829
+ suppressHydrationWarning: true
1830
+ })
1214
1831
  ),
1215
1832
  ...blocks.map((block) => {
1216
- if (decidedInProxy(block)) {
1833
+ if (block.groups.size === 0) {
1217
1834
  return (0, import_react.createElement)(
1218
1835
  import_react.Fragment,
1219
1836
  { key: block.key },
1220
- renderAllVariants(block, render, { bucket, track: trackVariants })
1837
+ renderItems(block.items, render, {
1838
+ bucket,
1839
+ index: block.index,
1840
+ track: trackVariants,
1841
+ defaultVariant: true
1842
+ })
1221
1843
  );
1222
1844
  }
1223
- const fallback = renderBuckets(block.items, render, {
1224
- bucket,
1225
- abTest: block.abTest,
1226
- index: block.index,
1227
- track: trackVariants && !block.personalized,
1228
- placeholder: block.personalized
1229
- });
1230
- if (!block.personalized) {
1231
- return (0, import_react.createElement)(import_react.Fragment, { key: block.key }, fallback);
1232
- }
1233
1845
  return (0, import_react.createElement)(
1234
- import_react.Suspense,
1235
- { key: block.key, fallback },
1236
- (0, import_react.createElement)(PersonalizedBlock, {
1237
- items: block.items,
1238
- decisions,
1846
+ import_react.Fragment,
1847
+ { key: block.key },
1848
+ renderVariants(block, block.groups, render, {
1239
1849
  bucket,
1240
- abTest: block.abTest,
1241
- index: block.index,
1242
- render,
1243
1850
  track: trackVariants
1851
+ }),
1852
+ (0, import_react.createElement)("script", {
1853
+ nonce,
1854
+ dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
1244
1855
  })
1245
1856
  );
1246
1857
  })
@@ -1252,33 +1863,59 @@ function createPreprClient2(options) {
1252
1863
  const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
1253
1864
  const client = createPreprClient({
1254
1865
  // The schema introspection goes through the Data Cache: shared across
1255
- // server instances, refreshed hourly and by revalidateTag on publish.
1866
+ // server instances, refreshed hourly and by a `schema.changed` webhook.
1256
1867
  schemaInit: {
1257
- next: { tags: cacheTags, revalidate: 3600 }
1868
+ next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
1258
1869
  },
1259
1870
  ...coreOptions
1260
1871
  });
1261
- const seenGroups = /* @__PURE__ */ new Map();
1262
- return {
1872
+ const nextClient = {
1263
1873
  async query(document2, variables, queryOptions = {}) {
1264
- const { personalize = false, ...rest } = queryOptions;
1265
- const init = rest.init;
1266
- const tagged = {
1874
+ const { personalize = false, tagItems = false, ...rest } = queryOptions;
1875
+ const { next, ...init } = rest.init ?? {};
1876
+ if (tagItems && typeof nextCache.cacheTag !== "function") {
1877
+ throw new Error("tagItems requires Next.js 16 or later.");
1878
+ }
1879
+ const options2 = {
1267
1880
  ...rest,
1268
- init: {
1881
+ // With tagItems, `next` is dropped too: tags or revalidate alongside
1882
+ // `no-store` are a conflict Next.js resolves by ignoring the store.
1883
+ // Otherwise tags are attached unconditionally; the core client drops
1884
+ // them again whenever the request ends up `no-store` (personalized,
1885
+ // per-request headers, or preview).
1886
+ init: tagItems ? { ...init, cache: "no-store" } : {
1269
1887
  ...init,
1270
1888
  next: {
1271
- tags: cacheTags,
1272
1889
  ...revalidate === void 0 ? {} : { revalidate },
1273
- ...init?.next
1890
+ ...next,
1891
+ // Merged, so a per-query tag never drops the client's own,
1892
+ // which the publish webhook relies on.
1893
+ tags: [...cacheTags, ...next?.tags ?? []]
1274
1894
  }
1275
1895
  }
1276
1896
  };
1277
- if (!personalize) return client.query(document2, variables, tagged);
1278
- return client.query(document2, variables, {
1279
- ...tagged,
1280
- headers: mergePreprHeaders(await getPreprHeaders(), rest.headers)
1281
- });
1897
+ if (personalize) {
1898
+ options2.headers = mergePreprHeaders(
1899
+ await getPreprHeaders(),
1900
+ rest.headers
1901
+ );
1902
+ }
1903
+ const result = await client.query(document2, variables, options2);
1904
+ if (tagItems) {
1905
+ let modelTags;
1906
+ try {
1907
+ modelTags = preprModelTagsFor(
1908
+ documentOf(document2),
1909
+ result.data,
1910
+ await clientSchemas.get(client)()
1911
+ );
1912
+ } catch {
1913
+ modelTags = [PREPR_ANY_ITEM_TAG];
1914
+ }
1915
+ nextCache.cacheTag(...cacheTags, ...next?.tags ?? [], ...modelTags);
1916
+ nextCache.cacheTag(...preprCacheTags(result.data));
1917
+ }
1918
+ return result;
1282
1919
  },
1283
1920
  async decide(document2, variables, queryOptions = {}) {
1284
1921
  return client.decide(document2, variables, {
@@ -1289,27 +1926,27 @@ function createPreprClient2(options) {
1289
1926
  )
1290
1927
  });
1291
1928
  },
1292
- hasAbTests: (document2, variables) => client.hasAbTests(document2, variables),
1293
- resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
1294
- client,
1295
- seenGroups,
1296
- request,
1297
- response,
1298
- document2,
1299
- variables,
1300
- options2
1301
- )
1929
+ hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
1302
1930
  };
1931
+ preprClientInternals.set(nextClient, { client });
1932
+ return nextClient;
1303
1933
  }
1304
1934
  // Annotate the CommonJS export names for ESM import in node:
1305
1935
  0 && (module.exports = {
1306
- PREPR_DECISIONS_COOKIE,
1936
+ PREPR_ANY_ITEM_TAG,
1307
1937
  PREPR_DEFAULT_VARIANT,
1308
1938
  PreprError,
1309
1939
  PreprStack,
1310
1940
  createPreprClient,
1941
+ createPreprProxy,
1942
+ decisionsFrom,
1311
1943
  isPersonalizedResponse,
1944
+ loadPreprSchema,
1945
+ preprCacheTags,
1946
+ preprModelTag,
1312
1947
  preprVariantProps,
1313
1948
  resolveVariants,
1314
- splitStack
1949
+ splitStack,
1950
+ toDecisionQuery,
1951
+ toVariantsQuery
1315
1952
  });