@preprio/toolkit 0.6.0-alpha.6 → 0.6.0-alpha.7
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-3EXK2LHI.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-OQGEL4TV.js → chunk-3QGWCYTL.js} +1 -1
- package/dist/{chunk-W6AVX6FI.js → chunk-PFJO4TYL.js} +52 -5
- package/dist/{chunk-Y7DWVOOY.js → chunk-U7TRFVK7.js} +1 -1
- package/dist/{chunk-NSQOGI5K.js → chunk-VQIPTUN4.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-CZK7SMBD.js} +58 -53
- package/dist/nextjs/components.cjs +175 -242
- package/dist/nextjs/components.d.cts +25 -21
- package/dist/nextjs/components.d.ts +25 -21
- package/dist/nextjs/components.js +167 -236
- package/dist/nextjs/graphql.cjs +372 -509
- package/dist/nextjs/graphql.d.cts +131 -135
- package/dist/nextjs/graphql.d.ts +131 -135
- package/dist/nextjs/graphql.js +307 -456
- package/dist/nextjs/index.cjs +114 -54
- package/dist/nextjs/index.d.cts +57 -33
- package/dist/nextjs/index.d.ts +57 -33
- package/dist/nextjs/index.js +108 -5
- package/dist/nuxt/{chunk-PF6GVWHV.js → chunk-JL33ENCM.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-S7S276V7.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.7";
|
|
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,220 @@ 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";
|
|
1594
|
-
const state = window;
|
|
1595
|
-
if (state.__preprAbBucket === bucket) return;
|
|
1596
|
-
state.__preprAbBucket = bucket;
|
|
1597
|
-
const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
|
|
1598
|
-
try {
|
|
1599
|
-
if (!state.__preprAbSheet) {
|
|
1600
|
-
const sheet = new CSSStyleSheet();
|
|
1601
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
1602
|
-
state.__preprAbSheet = sheet;
|
|
1603
|
-
}
|
|
1604
|
-
state.__preprAbSheet.replaceSync(css);
|
|
1605
|
-
} catch {
|
|
1606
|
-
if (!state.__preprAbSheet) {
|
|
1607
|
-
state.__preprAbSheet = document.createElement("style");
|
|
1608
|
-
document.head.appendChild(state.__preprAbSheet);
|
|
1609
|
-
}
|
|
1610
|
-
state.__preprAbSheet.textContent = css;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
function applyPreprDecisions(view) {
|
|
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
|
-
}
|
|
1453
|
+
// src/nextjs/decision-switch.ts
|
|
1454
|
+
function applyPreprDecisions(decision) {
|
|
1625
1455
|
const state = window;
|
|
1626
|
-
|
|
1627
|
-
state.
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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);
|
|
1640
1477
|
});
|
|
1478
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
1479
|
+
else element.removeAttribute("data-prepr-show");
|
|
1641
1480
|
});
|
|
1481
|
+
return true;
|
|
1482
|
+
}
|
|
1483
|
+
function startPreprDecision() {
|
|
1484
|
+
const state = window;
|
|
1485
|
+
const key = location.pathname + location.search;
|
|
1486
|
+
if (state.__preprPending && state.__preprPending.key === key) return;
|
|
1487
|
+
let last;
|
|
1642
1488
|
try {
|
|
1643
|
-
|
|
1644
|
-
const sheet = new CSSStyleSheet();
|
|
1645
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
1646
|
-
state.__preprDecisionSheet = sheet;
|
|
1647
|
-
}
|
|
1648
|
-
state.__preprDecisionSheet.replaceSync(css);
|
|
1489
|
+
last = JSON.parse(sessionStorage.getItem("prepr_decision") || "{}");
|
|
1649
1490
|
} catch {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1491
|
+
last = {};
|
|
1492
|
+
}
|
|
1493
|
+
const age = Date.now() - (last.at || 0);
|
|
1494
|
+
const value = last.key === key && typeof last.value === "string" && age >= 0 && age < 1e4 ? Promise.resolve(last.value) : fetch(key, {
|
|
1495
|
+
headers: { "Prepr-Decide": "1" },
|
|
1496
|
+
cache: "no-store",
|
|
1497
|
+
credentials: "same-origin"
|
|
1498
|
+
}).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((answer) => {
|
|
1499
|
+
try {
|
|
1500
|
+
sessionStorage.setItem(
|
|
1501
|
+
"prepr_decision",
|
|
1502
|
+
JSON.stringify({ key, value: answer, at: Date.now() })
|
|
1503
|
+
);
|
|
1504
|
+
} catch {
|
|
1653
1505
|
}
|
|
1654
|
-
|
|
1655
|
-
}
|
|
1506
|
+
return answer;
|
|
1507
|
+
});
|
|
1508
|
+
state.__preprPending = { key, value };
|
|
1509
|
+
value.then((answer) => {
|
|
1510
|
+
if (location.pathname + location.search === key) state.__preprApply(answer);
|
|
1511
|
+
});
|
|
1656
1512
|
}
|
|
1657
1513
|
|
|
1658
1514
|
// src/nextjs/stack.ts
|
|
1659
1515
|
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";
|
|
1516
|
+
var VARIANT_ATTRIBUTE = "data-prepr-v";
|
|
1666
1517
|
var DEFAULT_ATTRIBUTE = "data-prepr-default";
|
|
1667
|
-
var
|
|
1518
|
+
var NESTED_ATTRIBUTE = "data-prepr-nested";
|
|
1519
|
+
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}`;
|
|
1520
|
+
var NOSCRIPT_HTML = `<style>[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents!important;visibility:visible!important}</style>`;
|
|
1521
|
+
var DECISION_SCRIPT = `window.__preprApply=${applyPreprDecisions.toString()};(${startPreprDecision.toString()})();`;
|
|
1522
|
+
var APPLY_SCRIPT = "__preprApply()";
|
|
1668
1523
|
function variantKeysOf(items) {
|
|
1669
1524
|
return items.flatMap((item) => {
|
|
1670
|
-
const key = preprVariantProps(item)["data-prepr-variant-key"];
|
|
1525
|
+
const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
|
|
1671
1526
|
return key ? [key] : [];
|
|
1672
1527
|
});
|
|
1673
1528
|
}
|
|
1674
|
-
function
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
placeholder = false,
|
|
1682
|
-
defaultVariant = true
|
|
1683
|
-
} = options;
|
|
1684
|
-
const renderVariant = (variant, key) => {
|
|
1685
|
-
const resolved = resolveVariants(items, { decisions, bucket: variant });
|
|
1686
|
-
const ab = abTest && !bucket ? variant : void 0;
|
|
1687
|
-
const content = render(resolved, {
|
|
1688
|
-
index,
|
|
1689
|
-
placeholder,
|
|
1690
|
-
defaultVariant: defaultVariant && (ab === void 0 || ab === "A")
|
|
1691
|
-
});
|
|
1692
|
-
const variantKeys = track ? variantKeysOf(resolved) : [];
|
|
1693
|
-
if (variantKeys.length > 0) {
|
|
1694
|
-
return (0, import_react.createElement)(
|
|
1695
|
-
import_components.PreprVariantTracking,
|
|
1696
|
-
{ key, variantKeys, ab },
|
|
1697
|
-
content
|
|
1698
|
-
);
|
|
1529
|
+
function nestedKeyOf(value) {
|
|
1530
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1531
|
+
for (const child of Object.values(value)) {
|
|
1532
|
+
const items = Array.isArray(child) ? child : [child];
|
|
1533
|
+
for (const item of items) {
|
|
1534
|
+
const key = contextOf(item)?.variant_key ?? (item && nestedKeyOf(item));
|
|
1535
|
+
if (key) return key;
|
|
1699
1536
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1537
|
+
}
|
|
1538
|
+
return void 0;
|
|
1539
|
+
}
|
|
1540
|
+
function renderItems(items, render, options) {
|
|
1541
|
+
const resolved = resolveVariants(items, {
|
|
1542
|
+
decisions: options.decisions,
|
|
1543
|
+
bucket: options.bucket
|
|
1544
|
+
});
|
|
1545
|
+
const content = render(resolved, {
|
|
1546
|
+
index: options.index,
|
|
1547
|
+
defaultVariant: options.defaultVariant
|
|
1548
|
+
});
|
|
1549
|
+
const variantKeys = options.track ? variantKeysOf(resolved) : [];
|
|
1550
|
+
return variantKeys.length > 0 ? (0, import_react.createElement)(import_components.PreprVariantTracking, { variantKeys }, content) : content;
|
|
1551
|
+
}
|
|
1552
|
+
function decidedGroups(items, bucket) {
|
|
1553
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1554
|
+
const walk = (value) => {
|
|
1555
|
+
if (Array.isArray(value)) return value.forEach(walk);
|
|
1556
|
+
if (!value || typeof value !== "object") return;
|
|
1557
|
+
const ctx = contextOf(value);
|
|
1558
|
+
const decided = ctx?.kind === "PERSONALIZATION" || ctx?.kind === "AB_TEST" && !bucket;
|
|
1559
|
+
if (decided && ctx.variant_id) {
|
|
1560
|
+
const group = groups.get(ctx.group_id) ?? {
|
|
1561
|
+
variants: /* @__PURE__ */ new Set(),
|
|
1562
|
+
fallback: ctx.kind === "AB_TEST" ? "A" : PREPR_DEFAULT_VARIANT
|
|
1563
|
+
};
|
|
1564
|
+
group.variants.add(ctx.variant_id);
|
|
1565
|
+
groups.set(ctx.group_id, group);
|
|
1566
|
+
}
|
|
1567
|
+
Object.values(value).forEach(walk);
|
|
1702
1568
|
};
|
|
1703
|
-
|
|
1704
|
-
return
|
|
1705
|
-
}
|
|
1706
|
-
function
|
|
1707
|
-
const
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
)
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1569
|
+
walk(items);
|
|
1570
|
+
return groups;
|
|
1571
|
+
}
|
|
1572
|
+
function renderVariants(block, groups, render, options) {
|
|
1573
|
+
const top = block.key.startsWith("group:") ? block.key.slice("group:".length) : void 0;
|
|
1574
|
+
const nested = !(top !== void 0 && groups.size === 1);
|
|
1575
|
+
const choices = [...groups].map(([group, { variants, fallback }]) => {
|
|
1576
|
+
const ids = nested ? [fallback, ...variants] : [...variants];
|
|
1577
|
+
return [...new Set(ids)].map((variant) => [group, variant]);
|
|
1578
|
+
});
|
|
1579
|
+
const combinations = choices.reduce(
|
|
1580
|
+
(all, pairs) => all.flatMap((combo) => pairs.map((p) => [...combo, p])),
|
|
1581
|
+
[[]]
|
|
1582
|
+
);
|
|
1583
|
+
return combinations.map((combination) => {
|
|
1584
|
+
const pairs = combination.map(([group, variant]) => `${group}:${variant}`);
|
|
1585
|
+
const isDefault = combination.every(
|
|
1586
|
+
([group, variant]) => variant === groups.get(group).fallback
|
|
1587
|
+
);
|
|
1588
|
+
return (0, import_react.createElement)(
|
|
1718
1589
|
"div",
|
|
1719
1590
|
{
|
|
1720
|
-
key:
|
|
1721
|
-
[
|
|
1722
|
-
[
|
|
1723
|
-
...
|
|
1591
|
+
key: pairs.join(" "),
|
|
1592
|
+
[VARIANT_ATTRIBUTE]: pairs.join(" "),
|
|
1593
|
+
...isDefault ? { [DEFAULT_ATTRIBUTE]: "" } : {},
|
|
1594
|
+
...nested ? { [NESTED_ATTRIBUTE]: "" } : {},
|
|
1595
|
+
// The decision script marks the shown wrapper before hydration.
|
|
1596
|
+
suppressHydrationWarning: true
|
|
1724
1597
|
},
|
|
1725
|
-
|
|
1598
|
+
renderItems(block.items, render, {
|
|
1726
1599
|
bucket: options.bucket,
|
|
1727
|
-
decisions:
|
|
1728
|
-
abTest: false,
|
|
1600
|
+
decisions: new Map(combination.map(([g, v]) => [g, [v]])),
|
|
1729
1601
|
index: block.index,
|
|
1730
1602
|
track: options.track,
|
|
1731
|
-
defaultVariant:
|
|
1603
|
+
defaultVariant: isDefault
|
|
1732
1604
|
})
|
|
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
|
|
1605
|
+
);
|
|
1755
1606
|
});
|
|
1756
1607
|
}
|
|
1757
1608
|
function PreprStack({
|
|
1758
1609
|
items,
|
|
1759
|
-
decisions,
|
|
1760
1610
|
render,
|
|
1761
1611
|
bucket,
|
|
1762
1612
|
nonce,
|
|
1763
|
-
trackVariants = false
|
|
1764
|
-
aboveTheFold = 1
|
|
1613
|
+
trackVariants = false
|
|
1765
1614
|
}) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
const
|
|
1615
|
+
const blocks = splitStack(items).map((block) => ({
|
|
1616
|
+
...block,
|
|
1617
|
+
groups: decidedGroups(block.items, bucket)
|
|
1618
|
+
}));
|
|
1619
|
+
const varies = blocks.some((block) => block.groups.size > 0);
|
|
1771
1620
|
return (0, import_react.createElement)(
|
|
1772
1621
|
import_react.Fragment,
|
|
1773
1622
|
null,
|
|
1774
|
-
|
|
1623
|
+
varies && (0, import_react.createElement)(
|
|
1775
1624
|
import_react.Fragment,
|
|
1776
|
-
{ key: "prepr-
|
|
1625
|
+
{ key: "prepr-switch" },
|
|
1777
1626
|
(0, import_react.createElement)("style", {
|
|
1778
1627
|
nonce,
|
|
1779
|
-
dangerouslySetInnerHTML: {
|
|
1780
|
-
|
|
1781
|
-
|
|
1628
|
+
dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
|
|
1629
|
+
}),
|
|
1630
|
+
(0, import_react.createElement)("noscript", {
|
|
1631
|
+
dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
|
|
1782
1632
|
}),
|
|
1783
1633
|
(0, import_react.createElement)("script", {
|
|
1784
1634
|
nonce,
|
|
1785
|
-
dangerouslySetInnerHTML: { __html:
|
|
1635
|
+
dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
|
|
1786
1636
|
}),
|
|
1787
|
-
(0, import_react.createElement)(import_components.
|
|
1788
|
-
|
|
1637
|
+
(0, import_react.createElement)(import_components.PreprDecisionApplier),
|
|
1638
|
+
// Marked decided by the decision switch; never laid out.
|
|
1639
|
+
(0, import_react.createElement)("template", {
|
|
1640
|
+
"data-prepr-decision": "",
|
|
1641
|
+
suppressHydrationWarning: true
|
|
1642
|
+
})
|
|
1789
1643
|
),
|
|
1790
1644
|
...blocks.map((block) => {
|
|
1791
|
-
if (
|
|
1645
|
+
if (block.groups.size === 0) {
|
|
1792
1646
|
return (0, import_react.createElement)(
|
|
1793
1647
|
import_react.Fragment,
|
|
1794
1648
|
{ key: block.key },
|
|
1795
|
-
|
|
1649
|
+
renderItems(block.items, render, {
|
|
1650
|
+
bucket,
|
|
1651
|
+
index: block.index,
|
|
1652
|
+
track: trackVariants,
|
|
1653
|
+
defaultVariant: true
|
|
1654
|
+
})
|
|
1796
1655
|
);
|
|
1797
1656
|
}
|
|
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
1657
|
return (0, import_react.createElement)(
|
|
1809
|
-
import_react.
|
|
1810
|
-
{ key: block.key
|
|
1811
|
-
(
|
|
1812
|
-
items: block.items,
|
|
1813
|
-
decisions,
|
|
1658
|
+
import_react.Fragment,
|
|
1659
|
+
{ key: block.key },
|
|
1660
|
+
renderVariants(block, block.groups, render, {
|
|
1814
1661
|
bucket,
|
|
1815
|
-
abTest: block.abTest,
|
|
1816
|
-
index: block.index,
|
|
1817
|
-
render,
|
|
1818
1662
|
track: trackVariants
|
|
1663
|
+
}),
|
|
1664
|
+
(0, import_react.createElement)("script", {
|
|
1665
|
+
nonce,
|
|
1666
|
+
dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
|
|
1819
1667
|
})
|
|
1820
1668
|
);
|
|
1821
1669
|
})
|
|
@@ -1827,33 +1675,59 @@ function createPreprClient2(options) {
|
|
|
1827
1675
|
const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
|
|
1828
1676
|
const client = createPreprClient({
|
|
1829
1677
|
// The schema introspection goes through the Data Cache: shared across
|
|
1830
|
-
// server instances, refreshed hourly and by
|
|
1678
|
+
// server instances, refreshed hourly and by a `schema.changed` webhook.
|
|
1831
1679
|
schemaInit: {
|
|
1832
|
-
next: { tags: cacheTags, revalidate: 3600 }
|
|
1680
|
+
next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
|
|
1833
1681
|
},
|
|
1834
1682
|
...coreOptions
|
|
1835
1683
|
});
|
|
1836
|
-
const seenGroups = /* @__PURE__ */ new Map();
|
|
1837
1684
|
const nextClient = {
|
|
1838
1685
|
async query(document2, variables, queryOptions = {}) {
|
|
1839
|
-
const { personalize = false, ...rest } = queryOptions;
|
|
1840
|
-
const init = rest.init;
|
|
1841
|
-
|
|
1686
|
+
const { personalize = false, tagItems = false, ...rest } = queryOptions;
|
|
1687
|
+
const { next, ...init } = rest.init ?? {};
|
|
1688
|
+
if (tagItems && typeof nextCache.cacheTag !== "function") {
|
|
1689
|
+
throw new Error("tagItems requires Next.js 16 or later.");
|
|
1690
|
+
}
|
|
1691
|
+
const options2 = {
|
|
1842
1692
|
...rest,
|
|
1843
|
-
|
|
1693
|
+
// With tagItems, `next` is dropped too: tags or revalidate alongside
|
|
1694
|
+
// `no-store` are a conflict Next.js resolves by ignoring the store.
|
|
1695
|
+
// Otherwise tags are attached unconditionally; the core client drops
|
|
1696
|
+
// them again whenever the request ends up `no-store` (personalized,
|
|
1697
|
+
// per-request headers, or preview).
|
|
1698
|
+
init: tagItems ? { ...init, cache: "no-store" } : {
|
|
1844
1699
|
...init,
|
|
1845
1700
|
next: {
|
|
1846
|
-
tags: cacheTags,
|
|
1847
1701
|
...revalidate === void 0 ? {} : { revalidate },
|
|
1848
|
-
...
|
|
1702
|
+
...next,
|
|
1703
|
+
// Merged, so a per-query tag never drops the client's own,
|
|
1704
|
+
// which the publish webhook relies on.
|
|
1705
|
+
tags: [...cacheTags, ...next?.tags ?? []]
|
|
1849
1706
|
}
|
|
1850
1707
|
}
|
|
1851
1708
|
};
|
|
1852
|
-
if (
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1709
|
+
if (personalize) {
|
|
1710
|
+
options2.headers = mergePreprHeaders(
|
|
1711
|
+
await getPreprHeaders(),
|
|
1712
|
+
rest.headers
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const result = await client.query(document2, variables, options2);
|
|
1716
|
+
if (tagItems) {
|
|
1717
|
+
let modelTags;
|
|
1718
|
+
try {
|
|
1719
|
+
modelTags = preprModelTagsFor(
|
|
1720
|
+
documentOf(document2),
|
|
1721
|
+
result.data,
|
|
1722
|
+
await clientSchemas.get(client)()
|
|
1723
|
+
);
|
|
1724
|
+
} catch {
|
|
1725
|
+
modelTags = [PREPR_ANY_ITEM_TAG];
|
|
1726
|
+
}
|
|
1727
|
+
nextCache.cacheTag(...cacheTags, ...next?.tags ?? [], ...modelTags);
|
|
1728
|
+
nextCache.cacheTag(...preprCacheTags(result.data));
|
|
1729
|
+
}
|
|
1730
|
+
return result;
|
|
1857
1731
|
},
|
|
1858
1732
|
async decide(document2, variables, queryOptions = {}) {
|
|
1859
1733
|
return client.decide(document2, variables, {
|
|
@@ -1864,27 +1738,14 @@ function createPreprClient2(options) {
|
|
|
1864
1738
|
)
|
|
1865
1739
|
});
|
|
1866
1740
|
},
|
|
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
|
-
)
|
|
1741
|
+
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1877
1742
|
};
|
|
1878
|
-
preprClientInternals.set(nextClient, {
|
|
1879
|
-
client,
|
|
1880
|
-
seenGroups,
|
|
1881
|
-
url: coreOptions.url
|
|
1882
|
-
});
|
|
1743
|
+
preprClientInternals.set(nextClient, { client });
|
|
1883
1744
|
return nextClient;
|
|
1884
1745
|
}
|
|
1885
1746
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1886
1747
|
0 && (module.exports = {
|
|
1887
|
-
|
|
1748
|
+
PREPR_ANY_ITEM_TAG,
|
|
1888
1749
|
PREPR_DEFAULT_VARIANT,
|
|
1889
1750
|
PreprError,
|
|
1890
1751
|
PreprStack,
|
|
@@ -1893,6 +1754,8 @@ function createPreprClient2(options) {
|
|
|
1893
1754
|
decisionsFrom,
|
|
1894
1755
|
isPersonalizedResponse,
|
|
1895
1756
|
loadPreprSchema,
|
|
1757
|
+
preprCacheTags,
|
|
1758
|
+
preprModelTag,
|
|
1896
1759
|
preprVariantProps,
|
|
1897
1760
|
resolveVariants,
|
|
1898
1761
|
splitStack,
|