@preprio/toolkit 0.6.0-alpha.5 → 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-CD4LXNR3.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-5O7RGYHX.js → chunk-3QGWCYTL.js} +1 -1
- package/dist/{chunk-ZWKHACNA.js → chunk-PFJO4TYL.js} +52 -5
- package/dist/{chunk-HPWTLDLR.js → chunk-U7TRFVK7.js} +1 -1
- package/dist/{chunk-OH2PPC76.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-L4L6X3JJ.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 +374 -510
- package/dist/nextjs/graphql.d.cts +131 -135
- package/dist/nextjs/graphql.d.ts +131 -135
- package/dist/nextjs/graphql.js +309 -457
- 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-PJZMLKJ2.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-SKBW7RBD.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,65 +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
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
parseRefreshPaths(request.headers.get(PREPR_REFRESH_HEADER))
|
|
1553
|
-
);
|
|
1554
|
-
}
|
|
1555
|
-
const target = isPagePath(request.nextUrl.pathname) ? lookup(request.nextUrl) : null;
|
|
1556
|
-
if (target && preview) {
|
|
1434
|
+
const rendersPage = (request.method === "GET" || request.method === "HEAD") && isPagePath(request.nextUrl.pathname);
|
|
1435
|
+
const target = rendersPage ? lookup(request.nextUrl) : null;
|
|
1436
|
+
const decisionRequest = request.headers.has(PREPR_DECIDE_HEADER);
|
|
1437
|
+
if (target && preview && !decisionRequest) {
|
|
1557
1438
|
const draft = preprDraftRedirect(request, { preview });
|
|
1558
1439
|
if (draft) return draft;
|
|
1559
1440
|
}
|
|
@@ -1561,24 +1442,7 @@ function createPreprProxy(options) {
|
|
|
1561
1442
|
preview,
|
|
1562
1443
|
features: options.features
|
|
1563
1444
|
});
|
|
1564
|
-
if (
|
|
1565
|
-
await Promise.all([
|
|
1566
|
-
resolvePreprBucket(request, response, {
|
|
1567
|
-
url,
|
|
1568
|
-
preview,
|
|
1569
|
-
onlyIf: () => options.client.hasAbTests(target.document, target.variables)
|
|
1570
|
-
}),
|
|
1571
|
-
resolveDecisionsWith(
|
|
1572
|
-
client,
|
|
1573
|
-
seenGroups,
|
|
1574
|
-
request,
|
|
1575
|
-
response,
|
|
1576
|
-
target.document,
|
|
1577
|
-
target.variables,
|
|
1578
|
-
{ freshFor: options.freshFor, timeoutMs: options.timeoutMs }
|
|
1579
|
-
)
|
|
1580
|
-
]);
|
|
1581
|
-
}
|
|
1445
|
+
if (decisionRequest) return answerDecision(request, response, target);
|
|
1582
1446
|
return response;
|
|
1583
1447
|
};
|
|
1584
1448
|
}
|
|
@@ -1586,235 +1450,220 @@ function createPreprProxy(options) {
|
|
|
1586
1450
|
// src/nextjs/stack.ts
|
|
1587
1451
|
var import_react = require("react");
|
|
1588
1452
|
|
|
1589
|
-
// src/nextjs/
|
|
1590
|
-
function
|
|
1591
|
-
const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
|
|
1592
|
-
const bucket = match ? match[1] : "A";
|
|
1593
|
-
const state = window;
|
|
1594
|
-
if (state.__preprAbBucket === bucket) return;
|
|
1595
|
-
state.__preprAbBucket = bucket;
|
|
1596
|
-
const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
|
|
1597
|
-
try {
|
|
1598
|
-
if (!state.__preprAbSheet) {
|
|
1599
|
-
const sheet = new CSSStyleSheet();
|
|
1600
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
1601
|
-
state.__preprAbSheet = sheet;
|
|
1602
|
-
}
|
|
1603
|
-
state.__preprAbSheet.replaceSync(css);
|
|
1604
|
-
} catch {
|
|
1605
|
-
if (!state.__preprAbSheet) {
|
|
1606
|
-
state.__preprAbSheet = document.createElement("style");
|
|
1607
|
-
document.head.appendChild(state.__preprAbSheet);
|
|
1608
|
-
}
|
|
1609
|
-
state.__preprAbSheet.textContent = css;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
function applyPreprDecisions(view) {
|
|
1613
|
-
const viewState = window;
|
|
1614
|
-
const current = view === void 0 ? location.pathname + location.search : view;
|
|
1615
|
-
if (viewState.__preprDecisionsView === current) return;
|
|
1616
|
-
viewState.__preprDecisionsView = current;
|
|
1617
|
-
const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
|
|
1618
|
-
let raw;
|
|
1619
|
-
try {
|
|
1620
|
-
raw = match ? decodeURIComponent(match[1]) : "";
|
|
1621
|
-
} catch {
|
|
1622
|
-
raw = "";
|
|
1623
|
-
}
|
|
1453
|
+
// src/nextjs/decision-switch.ts
|
|
1454
|
+
function applyPreprDecisions(decision) {
|
|
1624
1455
|
const state = window;
|
|
1625
|
-
|
|
1626
|
-
state.
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
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);
|
|
1639
1477
|
});
|
|
1478
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
1479
|
+
else element.removeAttribute("data-prepr-show");
|
|
1640
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;
|
|
1641
1488
|
try {
|
|
1642
|
-
|
|
1643
|
-
const sheet = new CSSStyleSheet();
|
|
1644
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
|
|
1645
|
-
state.__preprDecisionSheet = sheet;
|
|
1646
|
-
}
|
|
1647
|
-
state.__preprDecisionSheet.replaceSync(css);
|
|
1489
|
+
last = JSON.parse(sessionStorage.getItem("prepr_decision") || "{}");
|
|
1648
1490
|
} catch {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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 {
|
|
1652
1505
|
}
|
|
1653
|
-
|
|
1654
|
-
}
|
|
1506
|
+
return answer;
|
|
1507
|
+
});
|
|
1508
|
+
state.__preprPending = { key, value };
|
|
1509
|
+
value.then((answer) => {
|
|
1510
|
+
if (location.pathname + location.search === key) state.__preprApply(answer);
|
|
1511
|
+
});
|
|
1655
1512
|
}
|
|
1656
1513
|
|
|
1657
1514
|
// src/nextjs/stack.ts
|
|
1658
1515
|
var import_components = require("./components.cjs");
|
|
1659
|
-
var
|
|
1660
|
-
var AB_BUCKETS = ["A", "B"];
|
|
1661
|
-
var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
|
|
1662
|
-
var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
|
|
1663
|
-
var GROUP_ATTRIBUTE = "data-prepr-group";
|
|
1664
|
-
var VARIANT_ATTRIBUTE = "data-prepr-variant";
|
|
1516
|
+
var VARIANT_ATTRIBUTE = "data-prepr-v";
|
|
1665
1517
|
var DEFAULT_ATTRIBUTE = "data-prepr-default";
|
|
1666
|
-
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()";
|
|
1667
1523
|
function variantKeysOf(items) {
|
|
1668
1524
|
return items.flatMap((item) => {
|
|
1669
|
-
const key = preprVariantProps(item)["data-prepr-variant-key"];
|
|
1525
|
+
const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
|
|
1670
1526
|
return key ? [key] : [];
|
|
1671
1527
|
});
|
|
1672
1528
|
}
|
|
1673
|
-
function
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
placeholder = false,
|
|
1681
|
-
defaultVariant = true
|
|
1682
|
-
} = options;
|
|
1683
|
-
const renderVariant = (variant, key) => {
|
|
1684
|
-
const resolved = resolveVariants(items, { decisions, bucket: variant });
|
|
1685
|
-
const ab = abTest && !bucket ? variant : void 0;
|
|
1686
|
-
const content = render(resolved, {
|
|
1687
|
-
index,
|
|
1688
|
-
placeholder,
|
|
1689
|
-
defaultVariant: defaultVariant && (ab === void 0 || ab === "A")
|
|
1690
|
-
});
|
|
1691
|
-
const variantKeys = track ? variantKeysOf(resolved) : [];
|
|
1692
|
-
if (variantKeys.length > 0) {
|
|
1693
|
-
return (0, import_react.createElement)(
|
|
1694
|
-
import_components.PreprVariantTracking,
|
|
1695
|
-
{ key, variantKeys, ab },
|
|
1696
|
-
content
|
|
1697
|
-
);
|
|
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;
|
|
1698
1536
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
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);
|
|
1701
1568
|
};
|
|
1702
|
-
|
|
1703
|
-
return
|
|
1704
|
-
}
|
|
1705
|
-
function
|
|
1706
|
-
const
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
)
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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)(
|
|
1717
1589
|
"div",
|
|
1718
1590
|
{
|
|
1719
|
-
key:
|
|
1720
|
-
[
|
|
1721
|
-
[
|
|
1722
|
-
...
|
|
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
|
|
1723
1597
|
},
|
|
1724
|
-
|
|
1598
|
+
renderItems(block.items, render, {
|
|
1725
1599
|
bucket: options.bucket,
|
|
1726
|
-
decisions:
|
|
1727
|
-
abTest: false,
|
|
1600
|
+
decisions: new Map(combination.map(([g, v]) => [g, [v]])),
|
|
1728
1601
|
index: block.index,
|
|
1729
1602
|
track: options.track,
|
|
1730
|
-
defaultVariant:
|
|
1603
|
+
defaultVariant: isDefault
|
|
1731
1604
|
})
|
|
1732
|
-
)
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
async function PersonalizedBlock({
|
|
1736
|
-
items,
|
|
1737
|
-
decisions,
|
|
1738
|
-
bucket,
|
|
1739
|
-
abTest,
|
|
1740
|
-
index,
|
|
1741
|
-
render,
|
|
1742
|
-
track
|
|
1743
|
-
}) {
|
|
1744
|
-
const decided = await decisions.catch((error) => {
|
|
1745
|
-
if (isFrameworkSignal(error)) throw error;
|
|
1746
|
-
return void 0;
|
|
1747
|
-
});
|
|
1748
|
-
return renderBuckets(items, render, {
|
|
1749
|
-
bucket,
|
|
1750
|
-
decisions: decided,
|
|
1751
|
-
abTest,
|
|
1752
|
-
index,
|
|
1753
|
-
track
|
|
1605
|
+
);
|
|
1754
1606
|
});
|
|
1755
1607
|
}
|
|
1756
1608
|
function PreprStack({
|
|
1757
1609
|
items,
|
|
1758
|
-
decisions,
|
|
1759
1610
|
render,
|
|
1760
1611
|
bucket,
|
|
1761
1612
|
nonce,
|
|
1762
|
-
trackVariants = false
|
|
1763
|
-
aboveTheFold = 1
|
|
1613
|
+
trackVariants = false
|
|
1764
1614
|
}) {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
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);
|
|
1770
1620
|
return (0, import_react.createElement)(
|
|
1771
1621
|
import_react.Fragment,
|
|
1772
1622
|
null,
|
|
1773
|
-
|
|
1623
|
+
varies && (0, import_react.createElement)(
|
|
1774
1624
|
import_react.Fragment,
|
|
1775
|
-
{ key: "prepr-
|
|
1625
|
+
{ key: "prepr-switch" },
|
|
1776
1626
|
(0, import_react.createElement)("style", {
|
|
1777
1627
|
nonce,
|
|
1778
|
-
dangerouslySetInnerHTML: {
|
|
1779
|
-
|
|
1780
|
-
|
|
1628
|
+
dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
|
|
1629
|
+
}),
|
|
1630
|
+
(0, import_react.createElement)("noscript", {
|
|
1631
|
+
dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
|
|
1781
1632
|
}),
|
|
1782
1633
|
(0, import_react.createElement)("script", {
|
|
1783
1634
|
nonce,
|
|
1784
|
-
dangerouslySetInnerHTML: { __html:
|
|
1635
|
+
dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
|
|
1785
1636
|
}),
|
|
1786
|
-
(0, import_react.createElement)(import_components.
|
|
1787
|
-
|
|
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
|
+
})
|
|
1788
1643
|
),
|
|
1789
1644
|
...blocks.map((block) => {
|
|
1790
|
-
if (
|
|
1645
|
+
if (block.groups.size === 0) {
|
|
1791
1646
|
return (0, import_react.createElement)(
|
|
1792
1647
|
import_react.Fragment,
|
|
1793
1648
|
{ key: block.key },
|
|
1794
|
-
|
|
1649
|
+
renderItems(block.items, render, {
|
|
1650
|
+
bucket,
|
|
1651
|
+
index: block.index,
|
|
1652
|
+
track: trackVariants,
|
|
1653
|
+
defaultVariant: true
|
|
1654
|
+
})
|
|
1795
1655
|
);
|
|
1796
1656
|
}
|
|
1797
|
-
const fallback = renderBuckets(block.items, render, {
|
|
1798
|
-
bucket,
|
|
1799
|
-
abTest: block.abTest,
|
|
1800
|
-
index: block.index,
|
|
1801
|
-
track: trackVariants && !block.personalized,
|
|
1802
|
-
placeholder: block.personalized
|
|
1803
|
-
});
|
|
1804
|
-
if (!block.personalized) {
|
|
1805
|
-
return (0, import_react.createElement)(import_react.Fragment, { key: block.key }, fallback);
|
|
1806
|
-
}
|
|
1807
1657
|
return (0, import_react.createElement)(
|
|
1808
|
-
import_react.
|
|
1809
|
-
{ key: block.key
|
|
1810
|
-
(
|
|
1811
|
-
items: block.items,
|
|
1812
|
-
decisions,
|
|
1658
|
+
import_react.Fragment,
|
|
1659
|
+
{ key: block.key },
|
|
1660
|
+
renderVariants(block, block.groups, render, {
|
|
1813
1661
|
bucket,
|
|
1814
|
-
abTest: block.abTest,
|
|
1815
|
-
index: block.index,
|
|
1816
|
-
render,
|
|
1817
1662
|
track: trackVariants
|
|
1663
|
+
}),
|
|
1664
|
+
(0, import_react.createElement)("script", {
|
|
1665
|
+
nonce,
|
|
1666
|
+
dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
|
|
1818
1667
|
})
|
|
1819
1668
|
);
|
|
1820
1669
|
})
|
|
@@ -1826,33 +1675,59 @@ function createPreprClient2(options) {
|
|
|
1826
1675
|
const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
|
|
1827
1676
|
const client = createPreprClient({
|
|
1828
1677
|
// The schema introspection goes through the Data Cache: shared across
|
|
1829
|
-
// server instances, refreshed hourly and by
|
|
1678
|
+
// server instances, refreshed hourly and by a `schema.changed` webhook.
|
|
1830
1679
|
schemaInit: {
|
|
1831
|
-
next: { tags: cacheTags, revalidate: 3600 }
|
|
1680
|
+
next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
|
|
1832
1681
|
},
|
|
1833
1682
|
...coreOptions
|
|
1834
1683
|
});
|
|
1835
|
-
const seenGroups = /* @__PURE__ */ new Map();
|
|
1836
1684
|
const nextClient = {
|
|
1837
1685
|
async query(document2, variables, queryOptions = {}) {
|
|
1838
|
-
const { personalize = false, ...rest } = queryOptions;
|
|
1839
|
-
const init = rest.init;
|
|
1840
|
-
|
|
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 = {
|
|
1841
1692
|
...rest,
|
|
1842
|
-
|
|
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" } : {
|
|
1843
1699
|
...init,
|
|
1844
1700
|
next: {
|
|
1845
|
-
tags: cacheTags,
|
|
1846
1701
|
...revalidate === void 0 ? {} : { revalidate },
|
|
1847
|
-
...
|
|
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 ?? []]
|
|
1848
1706
|
}
|
|
1849
1707
|
}
|
|
1850
1708
|
};
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
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;
|
|
1856
1731
|
},
|
|
1857
1732
|
async decide(document2, variables, queryOptions = {}) {
|
|
1858
1733
|
return client.decide(document2, variables, {
|
|
@@ -1863,27 +1738,14 @@ function createPreprClient2(options) {
|
|
|
1863
1738
|
)
|
|
1864
1739
|
});
|
|
1865
1740
|
},
|
|
1866
|
-
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1867
|
-
resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
|
|
1868
|
-
client,
|
|
1869
|
-
seenGroups,
|
|
1870
|
-
request,
|
|
1871
|
-
response,
|
|
1872
|
-
document2,
|
|
1873
|
-
variables,
|
|
1874
|
-
options2
|
|
1875
|
-
)
|
|
1741
|
+
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1876
1742
|
};
|
|
1877
|
-
preprClientInternals.set(nextClient, {
|
|
1878
|
-
client,
|
|
1879
|
-
seenGroups,
|
|
1880
|
-
url: coreOptions.url
|
|
1881
|
-
});
|
|
1743
|
+
preprClientInternals.set(nextClient, { client });
|
|
1882
1744
|
return nextClient;
|
|
1883
1745
|
}
|
|
1884
1746
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1885
1747
|
0 && (module.exports = {
|
|
1886
|
-
|
|
1748
|
+
PREPR_ANY_ITEM_TAG,
|
|
1887
1749
|
PREPR_DEFAULT_VARIANT,
|
|
1888
1750
|
PreprError,
|
|
1889
1751
|
PreprStack,
|
|
@@ -1892,6 +1754,8 @@ function createPreprClient2(options) {
|
|
|
1892
1754
|
decisionsFrom,
|
|
1893
1755
|
isPersonalizedResponse,
|
|
1894
1756
|
loadPreprSchema,
|
|
1757
|
+
preprCacheTags,
|
|
1758
|
+
preprModelTag,
|
|
1895
1759
|
preprVariantProps,
|
|
1896
1760
|
resolveVariants,
|
|
1897
1761
|
splitStack,
|