@preprio/toolkit 0.6.0-alpha.6 → 0.6.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -52
- package/dist/astro/{chunk-MWRA3732.js → chunk-R2YPGDVU.js} +1 -1
- package/dist/astro/graphql.cjs +58 -10
- package/dist/astro/graphql.d.cts +33 -38
- package/dist/astro/graphql.d.ts +33 -38
- package/dist/astro/graphql.js +59 -11
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/{chunk-Y7DWVOOY.js → chunk-2QGUP7VQ.js} +1 -1
- package/dist/{chunk-NSQOGI5K.js → chunk-ATPB7GEC.js} +1 -1
- package/dist/{chunk-W6AVX6FI.js → chunk-OY7OKO6Q.js} +52 -5
- package/dist/{chunk-OQGEL4TV.js → chunk-T7ESBVBI.js} +1 -1
- package/dist/graphql/codegen.cjs +51 -4
- package/dist/graphql/codegen.js +3 -3
- package/dist/graphql.cjs +89 -10
- package/dist/graphql.d.cts +69 -39
- package/dist/graphql.d.ts +69 -39
- package/dist/graphql.js +38 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/nextjs/{chunk-PIQF5ZI5.js → chunk-O2SBSBDG.js} +58 -53
- package/dist/nextjs/components.cjs +205 -236
- package/dist/nextjs/components.d.cts +27 -22
- package/dist/nextjs/components.d.ts +27 -22
- package/dist/nextjs/components.js +198 -231
- package/dist/nextjs/graphql.cjs +418 -508
- package/dist/nextjs/graphql.d.cts +131 -135
- package/dist/nextjs/graphql.d.ts +131 -135
- package/dist/nextjs/graphql.js +353 -455
- package/dist/nextjs/index.cjs +114 -54
- package/dist/nextjs/index.d.cts +59 -33
- package/dist/nextjs/index.d.ts +59 -33
- package/dist/nextjs/index.js +108 -5
- package/dist/nuxt/{chunk-PF6GVWHV.js → chunk-SX3MSVPK.js} +1 -1
- package/dist/nuxt/graphql.cjs +58 -10
- package/dist/nuxt/graphql.d.cts +33 -38
- package/dist/nuxt/graphql.d.ts +33 -38
- package/dist/nuxt/graphql.js +59 -11
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/{chunk-5K7HWMIF.js → chunk-UOASSHZO.js} +1 -1
- package/dist/sveltekit/graphql.cjs +58 -10
- package/dist/sveltekit/graphql.d.cts +33 -38
- package/dist/sveltekit/graphql.d.ts +33 -38
- package/dist/sveltekit/graphql.js +59 -11
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +1 -1
package/dist/nextjs/graphql.cjs
CHANGED
|
@@ -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,12 +17,20 @@ 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
|
-
|
|
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,
|
|
@@ -29,6 +39,8 @@ __export(graphql_exports, {
|
|
|
29
39
|
decisionsFrom: () => decisionsFrom,
|
|
30
40
|
isPersonalizedResponse: () => isPersonalizedResponse,
|
|
31
41
|
loadPreprSchema: () => loadPreprSchema,
|
|
42
|
+
preprCacheTags: () => preprCacheTags,
|
|
43
|
+
preprModelTag: () => preprModelTag,
|
|
32
44
|
preprVariantProps: () => preprVariantProps,
|
|
33
45
|
resolveVariants: () => resolveVariants,
|
|
34
46
|
splitStack: () => splitStack,
|
|
@@ -36,9 +48,60 @@ __export(graphql_exports, {
|
|
|
36
48
|
toVariantsQuery: () => toVariantsQuery
|
|
37
49
|
});
|
|
38
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
|
+
}
|
|
39
102
|
|
|
40
103
|
// src/version.ts
|
|
41
|
-
var VERSION = "0.6.0-alpha.
|
|
104
|
+
var VERSION = "0.6.0-alpha.8";
|
|
42
105
|
|
|
43
106
|
// src/core/features.ts
|
|
44
107
|
function isEnabled(config) {
|
|
@@ -202,7 +265,10 @@ function decisionSelectionSet(set, typeName, path, ctx) {
|
|
|
202
265
|
path,
|
|
203
266
|
ctx
|
|
204
267
|
);
|
|
205
|
-
if (pruned)
|
|
268
|
+
if (!pruned) continue;
|
|
269
|
+
if (fragment.typeCondition.name.value === typeName) {
|
|
270
|
+
selections.push(...pruned.selections);
|
|
271
|
+
} else {
|
|
206
272
|
selections.push(
|
|
207
273
|
inlineFragment(fragment.typeCondition.name.value, [
|
|
208
274
|
...pruned.selections
|
|
@@ -212,13 +278,19 @@ function decisionSelectionSet(set, typeName, path, ctx) {
|
|
|
212
278
|
continue;
|
|
213
279
|
}
|
|
214
280
|
if (selection.kind === "InlineFragment") {
|
|
281
|
+
const condition = selection.typeCondition?.name.value ?? typeName;
|
|
215
282
|
const pruned = decisionSelectionSet(
|
|
216
283
|
selection.selectionSet,
|
|
217
|
-
|
|
284
|
+
condition,
|
|
218
285
|
path,
|
|
219
286
|
ctx
|
|
220
287
|
);
|
|
221
|
-
if (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
|
+
}
|
|
222
294
|
continue;
|
|
223
295
|
}
|
|
224
296
|
if (!selection.selectionSet) continue;
|
|
@@ -292,20 +364,58 @@ function toDecisionDocument(document2, schema, groupPaths, options) {
|
|
|
292
364
|
const used = /* @__PURE__ */ new Set();
|
|
293
365
|
collectVariables(pruned, used);
|
|
294
366
|
collectVariables(operation.directives, used);
|
|
367
|
+
const shared = sharedContextFragment(schema);
|
|
295
368
|
return {
|
|
296
369
|
kind: "Document",
|
|
297
370
|
definitions: [
|
|
371
|
+
...shared ? [shared.definition] : [],
|
|
298
372
|
{
|
|
299
373
|
...operation,
|
|
300
374
|
name: operation.name && name(`${operation.name.value}Decision`),
|
|
301
375
|
variableDefinitions: (operation.variableDefinitions ?? []).filter(
|
|
302
376
|
(definition) => used.has(definition.variable.name.value)
|
|
303
377
|
),
|
|
304
|
-
selectionSet: pruned
|
|
378
|
+
selectionSet: shared ? shared.apply(pruned) : pruned
|
|
305
379
|
}
|
|
306
380
|
]
|
|
307
381
|
};
|
|
308
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
|
+
}
|
|
309
419
|
|
|
310
420
|
// src/core/variants/probe-document.ts
|
|
311
421
|
function probeSelectionSet(set, fragments) {
|
|
@@ -886,6 +996,7 @@ function createVaryProbe(run, options) {
|
|
|
886
996
|
}
|
|
887
997
|
|
|
888
998
|
// src/core/graphql/client.ts
|
|
999
|
+
var clientSchemas = /* @__PURE__ */ new WeakMap();
|
|
889
1000
|
function isParseableUrl(url) {
|
|
890
1001
|
try {
|
|
891
1002
|
new URL(url);
|
|
@@ -922,7 +1033,7 @@ function createPreprClient(options) {
|
|
|
922
1033
|
));
|
|
923
1034
|
const variantsDocuments = /* @__PURE__ */ new Map();
|
|
924
1035
|
const decisionDocuments = /* @__PURE__ */ new Map();
|
|
925
|
-
|
|
1036
|
+
const client = {
|
|
926
1037
|
async query(document2, variables, queryOptions = {}) {
|
|
927
1038
|
const { variants, ...rest } = queryOptions;
|
|
928
1039
|
if (!variants) return run(document2, variables, rest);
|
|
@@ -934,10 +1045,10 @@ function createPreprClient(options) {
|
|
|
934
1045
|
return run(derived, variables, rest);
|
|
935
1046
|
},
|
|
936
1047
|
async decide(document2, variables, queryOptions = {}) {
|
|
937
|
-
const { from,
|
|
1048
|
+
const { from, ...rest } = queryOptions;
|
|
938
1049
|
const paths = from === void 0 ? void 0 : personalizationPaths(from);
|
|
939
1050
|
if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
|
|
940
|
-
const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") :
|
|
1051
|
+
const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : "*";
|
|
941
1052
|
let byPaths = decisionDocuments.get(document2);
|
|
942
1053
|
if (!byPaths) {
|
|
943
1054
|
byPaths = /* @__PURE__ */ new Map();
|
|
@@ -945,9 +1056,7 @@ function createPreprClient(options) {
|
|
|
945
1056
|
}
|
|
946
1057
|
let derived = byPaths.get(signature);
|
|
947
1058
|
if (derived === void 0) {
|
|
948
|
-
derived = toDecisionDocument(document2, await loadSchema(), paths
|
|
949
|
-
topLevelOnly: !paths && topLevelOnly
|
|
950
|
-
});
|
|
1059
|
+
derived = toDecisionDocument(document2, await loadSchema(), paths);
|
|
951
1060
|
byPaths.set(signature, derived);
|
|
952
1061
|
}
|
|
953
1062
|
if (!derived) return /* @__PURE__ */ new Map();
|
|
@@ -961,6 +1070,8 @@ function createPreprClient(options) {
|
|
|
961
1070
|
(vary) => vary === null || SEGMENTS_VARY.test(vary)
|
|
962
1071
|
)
|
|
963
1072
|
};
|
|
1073
|
+
clientSchemas.set(client, loadSchema);
|
|
1074
|
+
return client;
|
|
964
1075
|
}
|
|
965
1076
|
|
|
966
1077
|
// src/core/graphql/schema-loader.ts
|
|
@@ -987,69 +1098,15 @@ async function loadPreprSchema(url, options = {}) {
|
|
|
987
1098
|
return schema;
|
|
988
1099
|
}
|
|
989
1100
|
|
|
990
|
-
// src/core/refresh.ts
|
|
991
|
-
var PREPR_REFRESH_COOKIE = "prepr_refresh";
|
|
992
|
-
var PREPR_REFRESH_HEADER = "Prepr-Refresh";
|
|
993
|
-
var REFRESH_MAX_PATHS = 5;
|
|
994
|
-
var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
|
|
995
|
-
function safePath(path) {
|
|
996
|
-
return SAFE_PATH.test(path) ? path : void 0;
|
|
997
|
-
}
|
|
998
|
-
function isFresh(at, freshFor, now = Date.now()) {
|
|
999
|
-
const age = now - at;
|
|
1000
|
-
return at > 0 && age >= 0 && age < freshFor;
|
|
1001
|
-
}
|
|
1002
|
-
function cleanEntries(entries) {
|
|
1003
|
-
const out = [];
|
|
1004
|
-
for (const entry of entries) {
|
|
1005
|
-
if (out.length === REFRESH_MAX_PATHS) break;
|
|
1006
|
-
if (safePath(entry.path) && !out.some((e) => e.path === entry.path)) {
|
|
1007
|
-
out.push(entry);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
return out;
|
|
1011
|
-
}
|
|
1012
|
-
function parseRefresh(raw) {
|
|
1013
|
-
return cleanEntries(
|
|
1014
|
-
(raw ?? "").split("|").map((pair) => {
|
|
1015
|
-
const split = pair.lastIndexOf("~");
|
|
1016
|
-
const at = Number(split < 0 ? "" : pair.slice(split + 1));
|
|
1017
|
-
return {
|
|
1018
|
-
path: split < 0 ? pair : pair.slice(0, split),
|
|
1019
|
-
at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
|
|
1020
|
-
};
|
|
1021
|
-
})
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
function serializeRefresh(entries) {
|
|
1025
|
-
return cleanEntries(entries).map((entry) => `${entry.path}~${Math.floor(entry.at)}`).join("|");
|
|
1026
|
-
}
|
|
1027
|
-
function parseRefreshPaths(header) {
|
|
1028
|
-
return cleanEntries(
|
|
1029
|
-
(header ?? "").split("|").map((path) => ({ path, at: 0 }))
|
|
1030
|
-
).map((entry) => entry.path);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
1101
|
// src/nextjs/decisions.ts
|
|
1034
|
-
var
|
|
1102
|
+
var PREPR_DECIDE_HEADER = "Prepr-Decide";
|
|
1103
|
+
var DEFAULT_DECISION = "*";
|
|
1035
1104
|
var DECISIONS_MAX_GROUPS = 40;
|
|
1036
1105
|
var SAFE_ID = /^[\w-]+$/;
|
|
1037
|
-
var SEEN_MAX_PAGES = 1e3;
|
|
1038
|
-
function parseDecisions(raw) {
|
|
1039
|
-
const out = /* @__PURE__ */ new Map();
|
|
1040
|
-
for (const pair of (raw ?? "").split("~")) {
|
|
1041
|
-
const at = pair.indexOf(":");
|
|
1042
|
-
const group = pair.slice(0, at);
|
|
1043
|
-
if (at < 1 || !SAFE_ID.test(group)) continue;
|
|
1044
|
-
out.set(
|
|
1045
|
-
group,
|
|
1046
|
-
pair.slice(at + 1).split("|").filter((variant) => SAFE_ID.test(variant))
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
|
-
return out;
|
|
1050
|
-
}
|
|
1051
1106
|
function serializeDecisions(decisions) {
|
|
1052
|
-
return [...decisions].
|
|
1107
|
+
return [...decisions].filter(([group]) => SAFE_ID.test(group)).slice(0, DECISIONS_MAX_GROUPS).map(
|
|
1108
|
+
([group, variants]) => `${group}:${variants.filter((v) => SAFE_ID.test(v)).join("|")}`
|
|
1109
|
+
).join("~");
|
|
1053
1110
|
}
|
|
1054
1111
|
function forwardedHeaders(request, response) {
|
|
1055
1112
|
const overridden = response.headers.get("x-middleware-override-headers");
|
|
@@ -1062,109 +1119,16 @@ function forwardedHeaders(request, response) {
|
|
|
1062
1119
|
return getPreprHeadersFromHeaders(headers2);
|
|
1063
1120
|
}
|
|
1064
1121
|
var preprClientInternals = /* @__PURE__ */ new WeakMap();
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
if (!await client.hasSegments(document2, variables)) return void 0;
|
|
1068
|
-
const decided = await client.decide(document2, variables, {
|
|
1069
|
-
topLevelOnly: true,
|
|
1070
|
-
headers: headers2,
|
|
1071
|
-
init: { signal: AbortSignal.timeout(timeoutMs) }
|
|
1072
|
-
});
|
|
1073
|
-
let byVariables = seenGroups.get(document2);
|
|
1074
|
-
if (!byVariables) {
|
|
1075
|
-
byVariables = /* @__PURE__ */ new Map();
|
|
1076
|
-
seenGroups.set(document2, byVariables);
|
|
1077
|
-
}
|
|
1078
|
-
const pageKey = JSON.stringify(variables ?? null);
|
|
1079
|
-
let groups = byVariables.get(pageKey);
|
|
1080
|
-
if (!groups) {
|
|
1081
|
-
if (byVariables.size >= SEEN_MAX_PAGES) {
|
|
1082
|
-
byVariables.delete(byVariables.keys().next().value);
|
|
1083
|
-
}
|
|
1084
|
-
groups = /* @__PURE__ */ new Set();
|
|
1085
|
-
byVariables.set(pageKey, groups);
|
|
1086
|
-
}
|
|
1087
|
-
for (const group of decided.keys()) groups.add(group);
|
|
1088
|
-
return decided;
|
|
1089
|
-
}
|
|
1090
|
-
function writeDecisions(request, response, decided, paths) {
|
|
1091
|
-
const next = /* @__PURE__ */ new Map();
|
|
1092
|
-
for (const decisions of decided) {
|
|
1093
|
-
for (const [group, variants] of decisions) {
|
|
1094
|
-
if (SAFE_ID.test(group) && !next.has(group)) {
|
|
1095
|
-
next.set(
|
|
1096
|
-
group,
|
|
1097
|
-
variants.filter((v) => SAFE_ID.test(v))
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
for (const [group, variants] of parseDecisions(
|
|
1103
|
-
request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
|
|
1104
|
-
)) {
|
|
1105
|
-
if (!next.has(group)) next.set(group, variants);
|
|
1106
|
-
}
|
|
1107
|
-
const options = {
|
|
1108
|
-
path: "/",
|
|
1109
|
-
sameSite: "lax",
|
|
1110
|
-
httpOnly: false,
|
|
1111
|
-
secure: request.nextUrl.protocol === "https:"
|
|
1112
|
-
};
|
|
1113
|
-
if (next.size > 0) {
|
|
1114
|
-
response.cookies.set(
|
|
1115
|
-
PREPR_DECISIONS_COOKIE,
|
|
1116
|
-
serializeDecisions(next),
|
|
1117
|
-
options
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1120
|
-
if (paths.length > 0) {
|
|
1121
|
-
const at = Date.now();
|
|
1122
|
-
response.cookies.set(
|
|
1123
|
-
PREPR_REFRESH_COOKIE,
|
|
1124
|
-
serializeRefresh([
|
|
1125
|
-
...paths.map((path) => ({ path, at })),
|
|
1126
|
-
...parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value)
|
|
1127
|
-
]),
|
|
1128
|
-
options
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
function pagePath(request) {
|
|
1133
|
-
return request.nextUrl.basePath + request.nextUrl.pathname;
|
|
1134
|
-
}
|
|
1135
|
-
function contextChanged(request, headers2) {
|
|
1136
|
-
return headers2.get("Prepr-Customer-Id-Created") === "true" || headers2.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
|
|
1137
|
-
(key) => key.startsWith("utm_")
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
|
|
1122
|
+
async function decideView(client, headers2, page, timeoutMs = 3e3) {
|
|
1123
|
+
const init = { signal: AbortSignal.timeout(timeoutMs) };
|
|
1141
1124
|
try {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
const stored = parseDecisions(
|
|
1147
|
-
request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
|
|
1148
|
-
);
|
|
1149
|
-
const decidedAt = parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value).find(
|
|
1150
|
-
(entry) => entry.path === path
|
|
1151
|
-
)?.at ?? 0;
|
|
1152
|
-
const fresh = isFresh(decidedAt, options?.freshFor ?? DEFAULT_FRESH_FOR_MS);
|
|
1153
|
-
if (fresh && known && !contextChanged(request, headers2) && [...known].every((group) => stored.has(group))) {
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
const decided = await decidePage(
|
|
1157
|
-
client,
|
|
1158
|
-
seenGroups,
|
|
1159
|
-
headers2,
|
|
1160
|
-
document2,
|
|
1161
|
-
variables,
|
|
1162
|
-
options?.timeoutMs
|
|
1163
|
-
);
|
|
1164
|
-
if (!decided || decided.size === 0) return;
|
|
1165
|
-
writeDecisions(request, response, [decided], [path]);
|
|
1125
|
+
const decided = page.decision ? decisionsFrom(
|
|
1126
|
+
(await client.query(page.decision, page.variables, { headers: headers2, init })).data
|
|
1127
|
+
) : await client.decide(page.document, page.variables, { headers: headers2, init });
|
|
1128
|
+
return serializeDecisions(decided);
|
|
1166
1129
|
} catch (error) {
|
|
1167
1130
|
if (isFrameworkSignal(error)) throw error;
|
|
1131
|
+
return DEFAULT_DECISION;
|
|
1168
1132
|
}
|
|
1169
1133
|
}
|
|
1170
1134
|
|
|
@@ -1181,66 +1145,6 @@ async function getPreprHeaders() {
|
|
|
1181
1145
|
// src/nextjs/proxy.ts
|
|
1182
1146
|
var import_server9 = require("next/server");
|
|
1183
1147
|
|
|
1184
|
-
// src/core/constants.ts
|
|
1185
|
-
var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
|
|
1186
|
-
var COOKIE_SEGMENT = "Prepr-Segments";
|
|
1187
|
-
var COOKIE_VARIANT = "Prepr-ABtesting";
|
|
1188
|
-
var COOKIE_UID = "__prepr_uid";
|
|
1189
|
-
var COOKIE_HUBSPOT = "hubspotutk";
|
|
1190
|
-
var PARAM_SEGMENT = "prepr_preview_segment";
|
|
1191
|
-
var PARAM_VARIANT = "prepr_preview_ab";
|
|
1192
|
-
var PARAM_HIDE_BAR = "prepr_hide_bar";
|
|
1193
|
-
var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
1194
|
-
|
|
1195
|
-
// src/nextjs/bucket.ts
|
|
1196
|
-
var PREPR_BUCKET_COOKIE = "prepr_ab_bucket";
|
|
1197
|
-
var isBucket = (value) => value === "A" || value === "B";
|
|
1198
|
-
function setBucketCookie(request, response, bucket) {
|
|
1199
|
-
response.cookies.set(PREPR_BUCKET_COOKIE, bucket, {
|
|
1200
|
-
path: "/",
|
|
1201
|
-
sameSite: "lax",
|
|
1202
|
-
// Readable on purpose: PreprStack's inline switch reads it before first
|
|
1203
|
-
// paint. It holds only "A" or "B".
|
|
1204
|
-
httpOnly: false,
|
|
1205
|
-
secure: request.nextUrl.protocol === "https:",
|
|
1206
|
-
maxAge: 60 * 60 * 24 * 365
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
async function resolvePreprBucket(request, response, options) {
|
|
1210
|
-
const stored = request.cookies.get(PREPR_BUCKET_COOKIE)?.value;
|
|
1211
|
-
const forced = response.headers.get("x-middleware-request-prepr-abtesting") ?? request.cookies.get(COOKIE_VARIANT)?.value;
|
|
1212
|
-
if (options.preview && isBucket(forced)) {
|
|
1213
|
-
if (forced !== stored) setBucketCookie(request, response, forced);
|
|
1214
|
-
return forced;
|
|
1215
|
-
}
|
|
1216
|
-
if (isBucket(stored)) return stored;
|
|
1217
|
-
if (options.onlyIf) {
|
|
1218
|
-
const needed = await Promise.resolve().then(options.onlyIf).catch(() => true);
|
|
1219
|
-
if (!needed) return "A";
|
|
1220
|
-
}
|
|
1221
|
-
const customerId = request.cookies.get(COOKIE_UID)?.value ?? response.cookies.get(COOKIE_UID)?.value;
|
|
1222
|
-
if (!customerId) return "A";
|
|
1223
|
-
try {
|
|
1224
|
-
const res = await fetch(options.url, {
|
|
1225
|
-
method: "POST",
|
|
1226
|
-
headers: {
|
|
1227
|
-
"Content-Type": "application/json",
|
|
1228
|
-
"Prepr-Customer-Id": customerId
|
|
1229
|
-
},
|
|
1230
|
-
body: JSON.stringify({ query: "{ __typename }" }),
|
|
1231
|
-
cache: "no-store",
|
|
1232
|
-
signal: AbortSignal.timeout(options.timeoutMs ?? 1500)
|
|
1233
|
-
});
|
|
1234
|
-
const bucket = res.headers.get("x-prepr-customer-bucket");
|
|
1235
|
-
if (isBucket(bucket)) {
|
|
1236
|
-
setBucketCookie(request, response, bucket);
|
|
1237
|
-
return bucket;
|
|
1238
|
-
}
|
|
1239
|
-
} catch {
|
|
1240
|
-
}
|
|
1241
|
-
return "A";
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
1148
|
// src/nextjs/draft.ts
|
|
1245
1149
|
var import_headers2 = require("next/headers");
|
|
1246
1150
|
var import_navigation = require("next/navigation");
|
|
@@ -1268,6 +1172,17 @@ function preprDraftRedirect(request, options) {
|
|
|
1268
1172
|
// src/nextjs/middleware.ts
|
|
1269
1173
|
var import_server8 = require("next/server");
|
|
1270
1174
|
|
|
1175
|
+
// src/core/constants.ts
|
|
1176
|
+
var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
|
|
1177
|
+
var COOKIE_SEGMENT = "Prepr-Segments";
|
|
1178
|
+
var COOKIE_VARIANT = "Prepr-ABtesting";
|
|
1179
|
+
var COOKIE_UID = "__prepr_uid";
|
|
1180
|
+
var COOKIE_HUBSPOT = "hubspotutk";
|
|
1181
|
+
var PARAM_SEGMENT = "prepr_preview_segment";
|
|
1182
|
+
var PARAM_VARIANT = "prepr_preview_ab";
|
|
1183
|
+
var PARAM_HIDE_BAR = "prepr_hide_bar";
|
|
1184
|
+
var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
1185
|
+
|
|
1271
1186
|
// src/core/middleware.ts
|
|
1272
1187
|
var UTM_PARAMS = [
|
|
1273
1188
|
"utm_source",
|
|
@@ -1478,7 +1393,7 @@ function createPreprProxy(options) {
|
|
|
1478
1393
|
"createPreprProxy"
|
|
1479
1394
|
);
|
|
1480
1395
|
}
|
|
1481
|
-
const { client
|
|
1396
|
+
const { client } = internals;
|
|
1482
1397
|
const preview = options.preview ?? false;
|
|
1483
1398
|
const allowed = async (request) => {
|
|
1484
1399
|
if (!options.onlyIf) return true;
|
|
@@ -1495,66 +1410,31 @@ function createPreprProxy(options) {
|
|
|
1495
1410
|
return null;
|
|
1496
1411
|
}
|
|
1497
1412
|
};
|
|
1498
|
-
async function
|
|
1499
|
-
|
|
1500
|
-
status: 204,
|
|
1501
|
-
headers: { "Cache-Control": "no-store" }
|
|
1502
|
-
});
|
|
1503
|
-
for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
|
|
1413
|
+
async function answerDecision(request, response, target) {
|
|
1414
|
+
let decision = DEFAULT_DECISION;
|
|
1504
1415
|
const site = request.headers.get("sec-fetch-site");
|
|
1505
|
-
if (request.method
|
|
1506
|
-
|
|
1416
|
+
if (target && request.method === "GET" && (site === null || site === "same-origin") && await allowed(request)) {
|
|
1417
|
+
decision = await decideView(
|
|
1418
|
+
client,
|
|
1419
|
+
mergePreprHeaders(forwardedHeaders(request, response)),
|
|
1420
|
+
target,
|
|
1421
|
+
options.timeoutMs
|
|
1422
|
+
);
|
|
1507
1423
|
}
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
const
|
|
1515
|
-
const decided = await Promise.all(
|
|
1516
|
-
paths.map(async (path) => {
|
|
1517
|
-
if (!own.has(path) || !path.startsWith(basePath)) return void 0;
|
|
1518
|
-
const pathname = path.slice(basePath.length) || "/";
|
|
1519
|
-
if (!isPagePath(pathname)) return void 0;
|
|
1520
|
-
const target = lookup(new URL(pathname, request.url));
|
|
1521
|
-
if (!target) return void 0;
|
|
1522
|
-
try {
|
|
1523
|
-
const decisions = await decidePage(
|
|
1524
|
-
client,
|
|
1525
|
-
seenGroups,
|
|
1526
|
-
headers2,
|
|
1527
|
-
target.document,
|
|
1528
|
-
target.variables,
|
|
1529
|
-
options.timeoutMs
|
|
1530
|
-
);
|
|
1531
|
-
return decisions?.size ? [path, decisions] : void 0;
|
|
1532
|
-
} catch (error) {
|
|
1533
|
-
if (isFrameworkSignal(error)) throw error;
|
|
1534
|
-
return void 0;
|
|
1535
|
-
}
|
|
1536
|
-
})
|
|
1537
|
-
);
|
|
1538
|
-
const found = decided.filter((entry) => entry !== void 0);
|
|
1539
|
-
writeDecisions(
|
|
1540
|
-
request,
|
|
1541
|
-
answer,
|
|
1542
|
-
found.map(([, decisions]) => decisions),
|
|
1543
|
-
found.map(([path]) => path)
|
|
1544
|
-
);
|
|
1424
|
+
const answer = new import_server9.NextResponse(decision, {
|
|
1425
|
+
headers: {
|
|
1426
|
+
"Cache-Control": "private, no-store",
|
|
1427
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
|
|
1545
1431
|
return answer;
|
|
1546
1432
|
}
|
|
1547
1433
|
return async (request) => {
|
|
1548
|
-
if (request.headers.has(PREPR_REFRESH_HEADER)) {
|
|
1549
|
-
return refresh(
|
|
1550
|
-
request,
|
|
1551
|
-
createPreprMiddleware(request, { preview, features: options.features }),
|
|
1552
|
-
parseRefreshPaths(request.headers.get(PREPR_REFRESH_HEADER))
|
|
1553
|
-
);
|
|
1554
|
-
}
|
|
1555
1434
|
const rendersPage = (request.method === "GET" || request.method === "HEAD") && isPagePath(request.nextUrl.pathname);
|
|
1556
1435
|
const target = rendersPage ? lookup(request.nextUrl) : null;
|
|
1557
|
-
|
|
1436
|
+
const decisionRequest = request.headers.has(PREPR_DECIDE_HEADER);
|
|
1437
|
+
if (target && preview && !decisionRequest) {
|
|
1558
1438
|
const draft = preprDraftRedirect(request, { preview });
|
|
1559
1439
|
if (draft) return draft;
|
|
1560
1440
|
}
|
|
@@ -1562,24 +1442,7 @@ function createPreprProxy(options) {
|
|
|
1562
1442
|
preview,
|
|
1563
1443
|
features: options.features
|
|
1564
1444
|
});
|
|
1565
|
-
if (
|
|
1566
|
-
await Promise.all([
|
|
1567
|
-
resolvePreprBucket(request, response, {
|
|
1568
|
-
url,
|
|
1569
|
-
preview,
|
|
1570
|
-
onlyIf: () => options.client.hasAbTests(target.document, target.variables)
|
|
1571
|
-
}),
|
|
1572
|
-
resolveDecisionsWith(
|
|
1573
|
-
client,
|
|
1574
|
-
seenGroups,
|
|
1575
|
-
request,
|
|
1576
|
-
response,
|
|
1577
|
-
target.document,
|
|
1578
|
-
target.variables,
|
|
1579
|
-
{ freshFor: options.freshFor, timeoutMs: options.timeoutMs }
|
|
1580
|
-
)
|
|
1581
|
-
]);
|
|
1582
|
-
}
|
|
1445
|
+
if (decisionRequest) return answerDecision(request, response, target);
|
|
1583
1446
|
return response;
|
|
1584
1447
|
};
|
|
1585
1448
|
}
|
|
@@ -1587,235 +1450,267 @@ function createPreprProxy(options) {
|
|
|
1587
1450
|
// src/nextjs/stack.ts
|
|
1588
1451
|
var import_react = require("react");
|
|
1589
1452
|
|
|
1590
|
-
// src/nextjs/
|
|
1591
|
-
function
|
|
1592
|
-
const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
|
|
1593
|
-
const bucket = match ? match[1] : "A";
|
|
1453
|
+
// src/nextjs/decision-switch.ts
|
|
1454
|
+
function applyPreprDecisions(decision) {
|
|
1594
1455
|
const state = window;
|
|
1595
|
-
|
|
1596
|
-
state.
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1456
|
+
const key = location.pathname + location.search;
|
|
1457
|
+
if (decision !== void 0) state.__preprDecision = { key, value: decision };
|
|
1458
|
+
const current = state.__preprDecision;
|
|
1459
|
+
if (!current || current.key !== key) return false;
|
|
1460
|
+
document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
|
|
1461
|
+
marker.setAttribute("data-prepr-decided", "");
|
|
1462
|
+
});
|
|
1463
|
+
const defaults = current.value === "*";
|
|
1464
|
+
const chosen = {};
|
|
1465
|
+
current.value.split("~").forEach((pair) => {
|
|
1466
|
+
const colon = pair.indexOf(":");
|
|
1467
|
+
if (colon > 0)
|
|
1468
|
+
chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
|
|
1469
|
+
});
|
|
1470
|
+
document.querySelectorAll("[data-prepr-v]").forEach((element) => {
|
|
1471
|
+
const nested = element.hasAttribute("data-prepr-nested");
|
|
1472
|
+
const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
|
|
1473
|
+
const colon = token.indexOf(":");
|
|
1474
|
+
const variant = token.slice(colon + 1);
|
|
1475
|
+
const variants = chosen[token.slice(0, colon)];
|
|
1476
|
+
return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
|
|
1477
|
+
});
|
|
1478
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
1479
|
+
else element.removeAttribute("data-prepr-show");
|
|
1480
|
+
});
|
|
1481
|
+
return true;
|
|
1482
|
+
}
|
|
1483
|
+
function requestPreprDecision(key, options = {}) {
|
|
1484
|
+
const fresh = 1e4;
|
|
1485
|
+
const keep = 18e5;
|
|
1486
|
+
const keepStatic = 36e5;
|
|
1487
|
+
const state = window;
|
|
1488
|
+
const asking = state.__preprAsking = state.__preprAsking || {};
|
|
1489
|
+
const read = () => {
|
|
1490
|
+
try {
|
|
1491
|
+
const all = JSON.parse(sessionStorage.getItem("prepr_decisions") || "{}");
|
|
1492
|
+
return all && typeof all === "object" ? all : {};
|
|
1493
|
+
} catch {
|
|
1494
|
+
return {};
|
|
1603
1495
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (!
|
|
1607
|
-
|
|
1608
|
-
|
|
1496
|
+
};
|
|
1497
|
+
const ask = () => {
|
|
1498
|
+
if (!asking[key]) {
|
|
1499
|
+
asking[key] = fetch(key, {
|
|
1500
|
+
headers: { "Prepr-Decide": "1" },
|
|
1501
|
+
cache: "no-store",
|
|
1502
|
+
credentials: "same-origin",
|
|
1503
|
+
// The proxy gives up on Prepr after its own `timeoutMs`; this only
|
|
1504
|
+
// covers a proxy that never answers.
|
|
1505
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(6e3) : void 0
|
|
1506
|
+
}).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((answer) => {
|
|
1507
|
+
delete asking[key];
|
|
1508
|
+
try {
|
|
1509
|
+
const all = read();
|
|
1510
|
+
const at = Date.now();
|
|
1511
|
+
Object.keys(all).forEach((stored) => {
|
|
1512
|
+
const entry = all[stored];
|
|
1513
|
+
if (!(entry && typeof entry.at === "number" && at - entry.at < keepStatic))
|
|
1514
|
+
delete all[stored];
|
|
1515
|
+
});
|
|
1516
|
+
const kept = all[key];
|
|
1517
|
+
if (answer !== "*" || !kept || kept.value === "*")
|
|
1518
|
+
all[key] = { value: answer, at };
|
|
1519
|
+
sessionStorage.setItem("prepr_decisions", JSON.stringify(all));
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1522
|
+
return answer;
|
|
1523
|
+
});
|
|
1609
1524
|
}
|
|
1610
|
-
|
|
1525
|
+
return asking[key];
|
|
1526
|
+
};
|
|
1527
|
+
const last = read()[key];
|
|
1528
|
+
const age = last && typeof last.at === "number" ? Date.now() - last.at : -1;
|
|
1529
|
+
const value = last && typeof last.value === "string" ? last.value : null;
|
|
1530
|
+
if (value !== null && !(value === "" && options.personalized) && age >= 0 && age < (value === "*" ? fresh : value === "" ? keepStatic : keep)) {
|
|
1531
|
+
if (value !== "" && age >= fresh) {
|
|
1532
|
+
ask().then((answer) => {
|
|
1533
|
+
if (answer !== "*" && answer !== value && options.onChange)
|
|
1534
|
+
options.onChange(answer);
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
return { result: value, value: Promise.resolve(value) };
|
|
1611
1538
|
}
|
|
1539
|
+
const pending = {
|
|
1540
|
+
value: ask()
|
|
1541
|
+
};
|
|
1542
|
+
pending.value.then((answer) => {
|
|
1543
|
+
pending.result = answer;
|
|
1544
|
+
});
|
|
1545
|
+
return pending;
|
|
1612
1546
|
}
|
|
1613
|
-
function
|
|
1614
|
-
const viewState = window;
|
|
1615
|
-
const current = view === void 0 ? location.pathname + location.search : view;
|
|
1616
|
-
if (viewState.__preprDecisionsView === current) return;
|
|
1617
|
-
viewState.__preprDecisionsView = current;
|
|
1618
|
-
const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
|
|
1619
|
-
let raw;
|
|
1620
|
-
try {
|
|
1621
|
-
raw = match ? decodeURIComponent(match[1]) : "";
|
|
1622
|
-
} catch {
|
|
1623
|
-
raw = "";
|
|
1624
|
-
}
|
|
1547
|
+
function startPreprDecision() {
|
|
1625
1548
|
const state = window;
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (at < 1 || !id.test(group)) return;
|
|
1634
|
-
const selector = '[data-prepr-group="' + group + '"]';
|
|
1635
|
-
css += selector + "{display:none!important}";
|
|
1636
|
-
pair.slice(at + 1).split("|").forEach((variant) => {
|
|
1637
|
-
if (id.test(variant)) {
|
|
1638
|
-
css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
|
|
1639
|
-
}
|
|
1640
|
-
});
|
|
1549
|
+
const key = location.pathname + location.search;
|
|
1550
|
+
const apply = (answer) => {
|
|
1551
|
+
if (location.pathname + location.search === key) state.__preprApply(answer);
|
|
1552
|
+
};
|
|
1553
|
+
const pending = state.__preprRequest(key, {
|
|
1554
|
+
onChange: apply,
|
|
1555
|
+
personalized: true
|
|
1641
1556
|
});
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
const sheet = new CSSStyleSheet();
|
|
1645
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
1646
|
-
state.__preprDecisionSheet = sheet;
|
|
1647
|
-
}
|
|
1648
|
-
state.__preprDecisionSheet.replaceSync(css);
|
|
1649
|
-
} catch {
|
|
1650
|
-
if (!state.__preprDecisionSheet) {
|
|
1651
|
-
state.__preprDecisionSheet = document.createElement("style");
|
|
1652
|
-
document.head.appendChild(state.__preprDecisionSheet);
|
|
1653
|
-
}
|
|
1654
|
-
state.__preprDecisionSheet.textContent = css;
|
|
1655
|
-
}
|
|
1557
|
+
if (pending.result !== void 0) apply(pending.result);
|
|
1558
|
+
else pending.value.then(apply);
|
|
1656
1559
|
}
|
|
1657
1560
|
|
|
1658
1561
|
// src/nextjs/stack.ts
|
|
1659
1562
|
var import_components = require("./components.cjs");
|
|
1660
|
-
var
|
|
1661
|
-
var AB_BUCKETS = ["A", "B"];
|
|
1662
|
-
var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
|
|
1663
|
-
var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
|
|
1664
|
-
var GROUP_ATTRIBUTE = "data-prepr-group";
|
|
1665
|
-
var VARIANT_ATTRIBUTE = "data-prepr-variant";
|
|
1563
|
+
var VARIANT_ATTRIBUTE = "data-prepr-v";
|
|
1666
1564
|
var DEFAULT_ATTRIBUTE = "data-prepr-default";
|
|
1667
|
-
var
|
|
1565
|
+
var NESTED_ATTRIBUTE = "data-prepr-nested";
|
|
1566
|
+
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}`;
|
|
1567
|
+
var NOSCRIPT_HTML = `<style>[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents!important;visibility:visible!important}</style>`;
|
|
1568
|
+
var DECISION_SCRIPT = `window.__preprApply=${applyPreprDecisions.toString()};window.__preprRequest=${requestPreprDecision.toString()};(${startPreprDecision.toString()})();`;
|
|
1569
|
+
var APPLY_SCRIPT = "__preprApply()";
|
|
1668
1570
|
function variantKeysOf(items) {
|
|
1669
1571
|
return items.flatMap((item) => {
|
|
1670
|
-
const key = preprVariantProps(item)["data-prepr-variant-key"];
|
|
1572
|
+
const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
|
|
1671
1573
|
return key ? [key] : [];
|
|
1672
1574
|
});
|
|
1673
1575
|
}
|
|
1674
|
-
function
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1576
|
+
function nestedKeyOf(value) {
|
|
1577
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1578
|
+
for (const child of Object.values(value)) {
|
|
1579
|
+
const items = Array.isArray(child) ? child : [child];
|
|
1580
|
+
for (const item of items) {
|
|
1581
|
+
const key = contextOf(item)?.variant_key ?? (item && nestedKeyOf(item));
|
|
1582
|
+
if (key) return key;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return void 0;
|
|
1586
|
+
}
|
|
1587
|
+
function renderItems(items, render, options) {
|
|
1588
|
+
const resolved = resolveVariants(items, {
|
|
1589
|
+
decisions: options.decisions,
|
|
1590
|
+
bucket: options.bucket
|
|
1591
|
+
});
|
|
1592
|
+
const content = render(resolved, {
|
|
1593
|
+
index: options.index,
|
|
1594
|
+
defaultVariant: options.defaultVariant
|
|
1595
|
+
});
|
|
1596
|
+
const variantKeys = options.track ? variantKeysOf(resolved) : [];
|
|
1597
|
+
return variantKeys.length > 0 ? (0, import_react.createElement)(import_components.PreprVariantTracking, { variantKeys }, content) : content;
|
|
1598
|
+
}
|
|
1599
|
+
function decidedGroups(items, bucket) {
|
|
1600
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1601
|
+
const walk = (value) => {
|
|
1602
|
+
if (Array.isArray(value)) return value.forEach(walk);
|
|
1603
|
+
if (!value || typeof value !== "object") return;
|
|
1604
|
+
const ctx = contextOf(value);
|
|
1605
|
+
const decided = ctx?.kind === "PERSONALIZATION" || ctx?.kind === "AB_TEST" && !bucket;
|
|
1606
|
+
if (decided && ctx.variant_id) {
|
|
1607
|
+
const group = groups.get(ctx.group_id) ?? {
|
|
1608
|
+
variants: /* @__PURE__ */ new Set(),
|
|
1609
|
+
fallback: ctx.kind === "AB_TEST" ? "A" : PREPR_DEFAULT_VARIANT
|
|
1610
|
+
};
|
|
1611
|
+
group.variants.add(ctx.variant_id);
|
|
1612
|
+
groups.set(ctx.group_id, group);
|
|
1699
1613
|
}
|
|
1700
|
-
|
|
1701
|
-
return content;
|
|
1614
|
+
Object.values(value).forEach(walk);
|
|
1702
1615
|
};
|
|
1703
|
-
|
|
1704
|
-
return
|
|
1705
|
-
}
|
|
1706
|
-
function
|
|
1707
|
-
const
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
)
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1616
|
+
walk(items);
|
|
1617
|
+
return groups;
|
|
1618
|
+
}
|
|
1619
|
+
function renderVariants(block, groups, render, options) {
|
|
1620
|
+
const top = block.key.startsWith("group:") ? block.key.slice("group:".length) : void 0;
|
|
1621
|
+
const nested = !(top !== void 0 && groups.size === 1);
|
|
1622
|
+
const choices = [...groups].map(([group, { variants, fallback }]) => {
|
|
1623
|
+
const ids = nested ? [fallback, ...variants] : [...variants];
|
|
1624
|
+
return [...new Set(ids)].map((variant) => [group, variant]);
|
|
1625
|
+
});
|
|
1626
|
+
const combinations = choices.reduce(
|
|
1627
|
+
(all, pairs) => all.flatMap((combo) => pairs.map((p) => [...combo, p])),
|
|
1628
|
+
[[]]
|
|
1629
|
+
);
|
|
1630
|
+
return combinations.map((combination) => {
|
|
1631
|
+
const pairs = combination.map(([group, variant]) => `${group}:${variant}`);
|
|
1632
|
+
const isDefault = combination.every(
|
|
1633
|
+
([group, variant]) => variant === groups.get(group).fallback
|
|
1634
|
+
);
|
|
1635
|
+
return (0, import_react.createElement)(
|
|
1718
1636
|
"div",
|
|
1719
1637
|
{
|
|
1720
|
-
key:
|
|
1721
|
-
[
|
|
1722
|
-
[
|
|
1723
|
-
...
|
|
1638
|
+
key: pairs.join(" "),
|
|
1639
|
+
[VARIANT_ATTRIBUTE]: pairs.join(" "),
|
|
1640
|
+
...isDefault ? { [DEFAULT_ATTRIBUTE]: "" } : {},
|
|
1641
|
+
...nested ? { [NESTED_ATTRIBUTE]: "" } : {},
|
|
1642
|
+
// The decision script marks the shown wrapper before hydration.
|
|
1643
|
+
suppressHydrationWarning: true
|
|
1724
1644
|
},
|
|
1725
|
-
|
|
1645
|
+
renderItems(block.items, render, {
|
|
1726
1646
|
bucket: options.bucket,
|
|
1727
|
-
decisions:
|
|
1728
|
-
abTest: false,
|
|
1647
|
+
decisions: new Map(combination.map(([g, v]) => [g, [v]])),
|
|
1729
1648
|
index: block.index,
|
|
1730
1649
|
track: options.track,
|
|
1731
|
-
defaultVariant:
|
|
1650
|
+
defaultVariant: isDefault
|
|
1732
1651
|
})
|
|
1733
|
-
)
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
async function PersonalizedBlock({
|
|
1737
|
-
items,
|
|
1738
|
-
decisions,
|
|
1739
|
-
bucket,
|
|
1740
|
-
abTest,
|
|
1741
|
-
index,
|
|
1742
|
-
render,
|
|
1743
|
-
track
|
|
1744
|
-
}) {
|
|
1745
|
-
const decided = await decisions.catch((error) => {
|
|
1746
|
-
if (isFrameworkSignal(error)) throw error;
|
|
1747
|
-
return void 0;
|
|
1748
|
-
});
|
|
1749
|
-
return renderBuckets(items, render, {
|
|
1750
|
-
bucket,
|
|
1751
|
-
decisions: decided,
|
|
1752
|
-
abTest,
|
|
1753
|
-
index,
|
|
1754
|
-
track
|
|
1652
|
+
);
|
|
1755
1653
|
});
|
|
1756
1654
|
}
|
|
1757
1655
|
function PreprStack({
|
|
1758
1656
|
items,
|
|
1759
|
-
decisions,
|
|
1760
1657
|
render,
|
|
1761
1658
|
bucket,
|
|
1762
1659
|
nonce,
|
|
1763
|
-
trackVariants = false
|
|
1764
|
-
aboveTheFold = 1
|
|
1660
|
+
trackVariants = false
|
|
1765
1661
|
}) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
const
|
|
1662
|
+
const blocks = splitStack(items).map((block) => ({
|
|
1663
|
+
...block,
|
|
1664
|
+
groups: decidedGroups(block.items, bucket)
|
|
1665
|
+
}));
|
|
1666
|
+
const varies = blocks.some((block) => block.groups.size > 0);
|
|
1771
1667
|
return (0, import_react.createElement)(
|
|
1772
1668
|
import_react.Fragment,
|
|
1773
1669
|
null,
|
|
1774
|
-
|
|
1670
|
+
varies && (0, import_react.createElement)(
|
|
1775
1671
|
import_react.Fragment,
|
|
1776
|
-
{ key: "prepr-
|
|
1672
|
+
{ key: "prepr-switch" },
|
|
1777
1673
|
(0, import_react.createElement)("style", {
|
|
1778
1674
|
nonce,
|
|
1779
|
-
dangerouslySetInnerHTML: {
|
|
1780
|
-
|
|
1781
|
-
|
|
1675
|
+
dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
|
|
1676
|
+
}),
|
|
1677
|
+
(0, import_react.createElement)("noscript", {
|
|
1678
|
+
dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
|
|
1782
1679
|
}),
|
|
1783
1680
|
(0, import_react.createElement)("script", {
|
|
1784
1681
|
nonce,
|
|
1785
|
-
dangerouslySetInnerHTML: { __html:
|
|
1682
|
+
dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
|
|
1786
1683
|
}),
|
|
1787
|
-
(0, import_react.createElement)(import_components.
|
|
1788
|
-
|
|
1684
|
+
(0, import_react.createElement)(import_components.PreprDecisionApplier),
|
|
1685
|
+
// Marked decided by the decision switch; never laid out.
|
|
1686
|
+
(0, import_react.createElement)("template", {
|
|
1687
|
+
"data-prepr-decision": "",
|
|
1688
|
+
suppressHydrationWarning: true
|
|
1689
|
+
})
|
|
1789
1690
|
),
|
|
1790
1691
|
...blocks.map((block) => {
|
|
1791
|
-
if (
|
|
1692
|
+
if (block.groups.size === 0) {
|
|
1792
1693
|
return (0, import_react.createElement)(
|
|
1793
1694
|
import_react.Fragment,
|
|
1794
1695
|
{ key: block.key },
|
|
1795
|
-
|
|
1696
|
+
renderItems(block.items, render, {
|
|
1697
|
+
bucket,
|
|
1698
|
+
index: block.index,
|
|
1699
|
+
track: trackVariants,
|
|
1700
|
+
defaultVariant: true
|
|
1701
|
+
})
|
|
1796
1702
|
);
|
|
1797
1703
|
}
|
|
1798
|
-
const fallback = renderBuckets(block.items, render, {
|
|
1799
|
-
bucket,
|
|
1800
|
-
abTest: block.abTest,
|
|
1801
|
-
index: block.index,
|
|
1802
|
-
track: trackVariants && !block.personalized,
|
|
1803
|
-
placeholder: block.personalized
|
|
1804
|
-
});
|
|
1805
|
-
if (!block.personalized) {
|
|
1806
|
-
return (0, import_react.createElement)(import_react.Fragment, { key: block.key }, fallback);
|
|
1807
|
-
}
|
|
1808
1704
|
return (0, import_react.createElement)(
|
|
1809
|
-
import_react.
|
|
1810
|
-
{ key: block.key
|
|
1811
|
-
(
|
|
1812
|
-
items: block.items,
|
|
1813
|
-
decisions,
|
|
1705
|
+
import_react.Fragment,
|
|
1706
|
+
{ key: block.key },
|
|
1707
|
+
renderVariants(block, block.groups, render, {
|
|
1814
1708
|
bucket,
|
|
1815
|
-
abTest: block.abTest,
|
|
1816
|
-
index: block.index,
|
|
1817
|
-
render,
|
|
1818
1709
|
track: trackVariants
|
|
1710
|
+
}),
|
|
1711
|
+
(0, import_react.createElement)("script", {
|
|
1712
|
+
nonce,
|
|
1713
|
+
dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
|
|
1819
1714
|
})
|
|
1820
1715
|
);
|
|
1821
1716
|
})
|
|
@@ -1827,33 +1722,59 @@ function createPreprClient2(options) {
|
|
|
1827
1722
|
const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
|
|
1828
1723
|
const client = createPreprClient({
|
|
1829
1724
|
// The schema introspection goes through the Data Cache: shared across
|
|
1830
|
-
// server instances, refreshed hourly and by
|
|
1725
|
+
// server instances, refreshed hourly and by a `schema.changed` webhook.
|
|
1831
1726
|
schemaInit: {
|
|
1832
|
-
next: { tags: cacheTags, revalidate: 3600 }
|
|
1727
|
+
next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
|
|
1833
1728
|
},
|
|
1834
1729
|
...coreOptions
|
|
1835
1730
|
});
|
|
1836
|
-
const seenGroups = /* @__PURE__ */ new Map();
|
|
1837
1731
|
const nextClient = {
|
|
1838
1732
|
async query(document2, variables, queryOptions = {}) {
|
|
1839
|
-
const { personalize = false, ...rest } = queryOptions;
|
|
1840
|
-
const init = rest.init;
|
|
1841
|
-
|
|
1733
|
+
const { personalize = false, tagItems = false, ...rest } = queryOptions;
|
|
1734
|
+
const { next, ...init } = rest.init ?? {};
|
|
1735
|
+
if (tagItems && typeof nextCache.cacheTag !== "function") {
|
|
1736
|
+
throw new Error("tagItems requires Next.js 16 or later.");
|
|
1737
|
+
}
|
|
1738
|
+
const options2 = {
|
|
1842
1739
|
...rest,
|
|
1843
|
-
|
|
1740
|
+
// With tagItems, `next` is dropped too: tags or revalidate alongside
|
|
1741
|
+
// `no-store` are a conflict Next.js resolves by ignoring the store.
|
|
1742
|
+
// Otherwise tags are attached unconditionally; the core client drops
|
|
1743
|
+
// them again whenever the request ends up `no-store` (personalized,
|
|
1744
|
+
// per-request headers, or preview).
|
|
1745
|
+
init: tagItems ? { ...init, cache: "no-store" } : {
|
|
1844
1746
|
...init,
|
|
1845
1747
|
next: {
|
|
1846
|
-
tags: cacheTags,
|
|
1847
1748
|
...revalidate === void 0 ? {} : { revalidate },
|
|
1848
|
-
...
|
|
1749
|
+
...next,
|
|
1750
|
+
// Merged, so a per-query tag never drops the client's own,
|
|
1751
|
+
// which the publish webhook relies on.
|
|
1752
|
+
tags: [...cacheTags, ...next?.tags ?? []]
|
|
1849
1753
|
}
|
|
1850
1754
|
}
|
|
1851
1755
|
};
|
|
1852
|
-
if (
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1756
|
+
if (personalize) {
|
|
1757
|
+
options2.headers = mergePreprHeaders(
|
|
1758
|
+
await getPreprHeaders(),
|
|
1759
|
+
rest.headers
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
const result = await client.query(document2, variables, options2);
|
|
1763
|
+
if (tagItems) {
|
|
1764
|
+
let modelTags;
|
|
1765
|
+
try {
|
|
1766
|
+
modelTags = preprModelTagsFor(
|
|
1767
|
+
documentOf(document2),
|
|
1768
|
+
result.data,
|
|
1769
|
+
await clientSchemas.get(client)()
|
|
1770
|
+
);
|
|
1771
|
+
} catch {
|
|
1772
|
+
modelTags = [PREPR_ANY_ITEM_TAG];
|
|
1773
|
+
}
|
|
1774
|
+
nextCache.cacheTag(...cacheTags, ...next?.tags ?? [], ...modelTags);
|
|
1775
|
+
nextCache.cacheTag(...preprCacheTags(result.data));
|
|
1776
|
+
}
|
|
1777
|
+
return result;
|
|
1857
1778
|
},
|
|
1858
1779
|
async decide(document2, variables, queryOptions = {}) {
|
|
1859
1780
|
return client.decide(document2, variables, {
|
|
@@ -1864,27 +1785,14 @@ function createPreprClient2(options) {
|
|
|
1864
1785
|
)
|
|
1865
1786
|
});
|
|
1866
1787
|
},
|
|
1867
|
-
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1868
|
-
resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
|
|
1869
|
-
client,
|
|
1870
|
-
seenGroups,
|
|
1871
|
-
request,
|
|
1872
|
-
response,
|
|
1873
|
-
document2,
|
|
1874
|
-
variables,
|
|
1875
|
-
options2
|
|
1876
|
-
)
|
|
1788
|
+
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1877
1789
|
};
|
|
1878
|
-
preprClientInternals.set(nextClient, {
|
|
1879
|
-
client,
|
|
1880
|
-
seenGroups,
|
|
1881
|
-
url: coreOptions.url
|
|
1882
|
-
});
|
|
1790
|
+
preprClientInternals.set(nextClient, { client });
|
|
1883
1791
|
return nextClient;
|
|
1884
1792
|
}
|
|
1885
1793
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1886
1794
|
0 && (module.exports = {
|
|
1887
|
-
|
|
1795
|
+
PREPR_ANY_ITEM_TAG,
|
|
1888
1796
|
PREPR_DEFAULT_VARIANT,
|
|
1889
1797
|
PreprError,
|
|
1890
1798
|
PreprStack,
|
|
@@ -1893,6 +1801,8 @@ function createPreprClient2(options) {
|
|
|
1893
1801
|
decisionsFrom,
|
|
1894
1802
|
isPersonalizedResponse,
|
|
1895
1803
|
loadPreprSchema,
|
|
1804
|
+
preprCacheTags,
|
|
1805
|
+
preprModelTag,
|
|
1896
1806
|
preprVariantProps,
|
|
1897
1807
|
resolveVariants,
|
|
1898
1808
|
splitStack,
|