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