@preprio/toolkit 0.6.0-alpha.1 → 0.6.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -61
- package/dist/astro/{chunk-O7N6GPAH.js → chunk-CG2QBHNU.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-VI6QZTBM.js → chunk-LIDUJWWJ.js} +1 -1
- package/dist/{chunk-TBKSBXGH.js → chunk-M3RBYJ5J.js} +52 -5
- package/dist/{chunk-3JLWZQ37.js → chunk-SF6TN4JR.js} +1 -1
- package/dist/{chunk-YWIKKWBR.js → chunk-TN6SNAR2.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-B4GVZEOL.js +549 -0
- package/dist/nextjs/components.cjs +297 -81
- package/dist/nextjs/components.d.cts +26 -11
- package/dist/nextjs/components.d.ts +26 -11
- package/dist/nextjs/components.js +288 -73
- package/dist/nextjs/graphql.cjs +856 -266
- package/dist/nextjs/graphql.d.cts +184 -116
- package/dist/nextjs/graphql.d.ts +184 -116
- package/dist/nextjs/graphql.js +543 -266
- package/dist/nextjs/index.cjs +116 -54
- package/dist/nextjs/index.d.cts +61 -23
- package/dist/nextjs/index.d.ts +61 -23
- package/dist/nextjs/index.js +97 -299
- package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.cts} +1 -1
- package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.ts} +1 -1
- package/dist/nuxt/{chunk-IQDSBPDN.js → chunk-FMC3DP7N.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-OKYZ6HPH.js → chunk-AJPKBAHO.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/chunk-BQ6IGSH6.js +0 -231
package/dist/nextjs/graphql.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PREPR_ANY_ITEM_TAG,
|
|
3
|
+
PREPR_SCHEMA_TAG,
|
|
2
4
|
PreprError,
|
|
5
|
+
createPreprMiddleware,
|
|
3
6
|
getPreprHeaders,
|
|
4
7
|
getPreprHeadersFromHeaders,
|
|
5
8
|
isPersonalizedResponse,
|
|
9
|
+
preprCacheTags,
|
|
10
|
+
preprDraftRedirect,
|
|
11
|
+
preprModelTag,
|
|
12
|
+
preprModelTagsFor,
|
|
6
13
|
validatePreprToken
|
|
7
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-B4GVZEOL.js";
|
|
15
|
+
|
|
16
|
+
// src/nextjs/graphql.ts
|
|
17
|
+
import * as nextCache from "next/cache";
|
|
8
18
|
|
|
9
19
|
// src/core/variants/constants.ts
|
|
10
20
|
var PREPR_CONTEXT_ALIAS = "_preprContext";
|
|
@@ -93,7 +103,10 @@ function decisionSelectionSet(set, typeName, path, ctx) {
|
|
|
93
103
|
path,
|
|
94
104
|
ctx
|
|
95
105
|
);
|
|
96
|
-
if (pruned)
|
|
106
|
+
if (!pruned) continue;
|
|
107
|
+
if (fragment.typeCondition.name.value === typeName) {
|
|
108
|
+
selections.push(...pruned.selections);
|
|
109
|
+
} else {
|
|
97
110
|
selections.push(
|
|
98
111
|
inlineFragment(fragment.typeCondition.name.value, [
|
|
99
112
|
...pruned.selections
|
|
@@ -103,13 +116,19 @@ function decisionSelectionSet(set, typeName, path, ctx) {
|
|
|
103
116
|
continue;
|
|
104
117
|
}
|
|
105
118
|
if (selection.kind === "InlineFragment") {
|
|
119
|
+
const condition = selection.typeCondition?.name.value ?? typeName;
|
|
106
120
|
const pruned = decisionSelectionSet(
|
|
107
121
|
selection.selectionSet,
|
|
108
|
-
|
|
122
|
+
condition,
|
|
109
123
|
path,
|
|
110
124
|
ctx
|
|
111
125
|
);
|
|
112
|
-
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
|
+
}
|
|
113
132
|
continue;
|
|
114
133
|
}
|
|
115
134
|
if (!selection.selectionSet) continue;
|
|
@@ -183,20 +202,58 @@ function toDecisionDocument(document2, schema, groupPaths, options) {
|
|
|
183
202
|
const used = /* @__PURE__ */ new Set();
|
|
184
203
|
collectVariables(pruned, used);
|
|
185
204
|
collectVariables(operation.directives, used);
|
|
205
|
+
const shared = sharedContextFragment(schema);
|
|
186
206
|
return {
|
|
187
207
|
kind: "Document",
|
|
188
208
|
definitions: [
|
|
209
|
+
...shared ? [shared.definition] : [],
|
|
189
210
|
{
|
|
190
211
|
...operation,
|
|
191
212
|
name: operation.name && name(`${operation.name.value}Decision`),
|
|
192
213
|
variableDefinitions: (operation.variableDefinitions ?? []).filter(
|
|
193
214
|
(definition) => used.has(definition.variable.name.value)
|
|
194
215
|
),
|
|
195
|
-
selectionSet: pruned
|
|
216
|
+
selectionSet: shared ? shared.apply(pruned) : pruned
|
|
196
217
|
}
|
|
197
218
|
]
|
|
198
219
|
};
|
|
199
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
|
+
}
|
|
200
257
|
|
|
201
258
|
// src/core/variants/probe-document.ts
|
|
202
259
|
function probeSelectionSet(set, fragments) {
|
|
@@ -770,6 +827,7 @@ function createVaryProbe(run, options) {
|
|
|
770
827
|
}
|
|
771
828
|
|
|
772
829
|
// src/core/graphql/client.ts
|
|
830
|
+
var clientSchemas = /* @__PURE__ */ new WeakMap();
|
|
773
831
|
function isParseableUrl(url) {
|
|
774
832
|
try {
|
|
775
833
|
new URL(url);
|
|
@@ -806,7 +864,7 @@ function createPreprClient(options) {
|
|
|
806
864
|
));
|
|
807
865
|
const variantsDocuments = /* @__PURE__ */ new Map();
|
|
808
866
|
const decisionDocuments = /* @__PURE__ */ new Map();
|
|
809
|
-
|
|
867
|
+
const client = {
|
|
810
868
|
async query(document2, variables, queryOptions = {}) {
|
|
811
869
|
const { variants, ...rest } = queryOptions;
|
|
812
870
|
if (!variants) return run(document2, variables, rest);
|
|
@@ -818,10 +876,10 @@ function createPreprClient(options) {
|
|
|
818
876
|
return run(derived, variables, rest);
|
|
819
877
|
},
|
|
820
878
|
async decide(document2, variables, queryOptions = {}) {
|
|
821
|
-
const { from,
|
|
879
|
+
const { from, ...rest } = queryOptions;
|
|
822
880
|
const paths = from === void 0 ? void 0 : personalizationPaths(from);
|
|
823
881
|
if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
|
|
824
|
-
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("|") : "*";
|
|
825
883
|
let byPaths = decisionDocuments.get(document2);
|
|
826
884
|
if (!byPaths) {
|
|
827
885
|
byPaths = /* @__PURE__ */ new Map();
|
|
@@ -829,9 +887,7 @@ function createPreprClient(options) {
|
|
|
829
887
|
}
|
|
830
888
|
let derived = byPaths.get(signature);
|
|
831
889
|
if (derived === void 0) {
|
|
832
|
-
derived = toDecisionDocument(document2, await loadSchema(), paths
|
|
833
|
-
topLevelOnly: !paths && topLevelOnly
|
|
834
|
-
});
|
|
890
|
+
derived = toDecisionDocument(document2, await loadSchema(), paths);
|
|
835
891
|
byPaths.set(signature, derived);
|
|
836
892
|
}
|
|
837
893
|
if (!derived) return /* @__PURE__ */ new Map();
|
|
@@ -845,6 +901,8 @@ function createPreprClient(options) {
|
|
|
845
901
|
(vary) => vary === null || SEGMENTS_VARY.test(vary)
|
|
846
902
|
)
|
|
847
903
|
};
|
|
904
|
+
clientSchemas.set(client, loadSchema);
|
|
905
|
+
return client;
|
|
848
906
|
}
|
|
849
907
|
|
|
850
908
|
// src/core/graphql/schema-loader.ts
|
|
@@ -872,25 +930,62 @@ async function loadPreprSchema(url, options = {}) {
|
|
|
872
930
|
}
|
|
873
931
|
|
|
874
932
|
// src/nextjs/decisions.ts
|
|
933
|
+
var PREPR_DECIDE_HEADER = "Prepr-Decide";
|
|
934
|
+
var DEFAULT_DECISION = "*";
|
|
875
935
|
var PREPR_DECISIONS_COOKIE = "prepr_decisions";
|
|
876
|
-
var
|
|
877
|
-
var
|
|
878
|
-
var
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
)
|
|
936
|
+
var DECISION_FRESH_MS = 1e4;
|
|
937
|
+
var DECISION_KEEP_MS = 18e5;
|
|
938
|
+
var DECISION_KEEP_STATIC_MS = 36e5;
|
|
939
|
+
var STORED_MAX_ENTRIES = 20;
|
|
940
|
+
var STORED_MAX_LENGTH = 3e3;
|
|
941
|
+
function readStoredDecisions(raw) {
|
|
942
|
+
const stored = /* @__PURE__ */ Object.create(null);
|
|
943
|
+
if (!raw) return stored;
|
|
944
|
+
try {
|
|
945
|
+
const parsed = JSON.parse(raw);
|
|
946
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
947
|
+
return stored;
|
|
948
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
949
|
+
if (key.startsWith("/") && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number") {
|
|
950
|
+
stored[key] = [entry[0], entry[1]];
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
return stored;
|
|
956
|
+
}
|
|
957
|
+
function usableDecision(stored, key, now) {
|
|
958
|
+
const entry = stored[key];
|
|
959
|
+
if (!entry) return void 0;
|
|
960
|
+
const [value, at] = entry;
|
|
961
|
+
const age = now - at;
|
|
962
|
+
const keep = value === DEFAULT_DECISION ? DECISION_FRESH_MS : value === "" ? DECISION_KEEP_STATIC_MS : DECISION_KEEP_MS;
|
|
963
|
+
return age >= 0 && age < keep ? value : void 0;
|
|
964
|
+
}
|
|
965
|
+
function writeStoredDecisions(stored, key, value, now) {
|
|
966
|
+
const kept = usableDecision(stored, key, now);
|
|
967
|
+
const entries = Object.entries(stored).filter(
|
|
968
|
+
([stale]) => stale !== key && usableDecision(stored, stale, now) !== void 0
|
|
969
|
+
);
|
|
970
|
+
entries.push([
|
|
971
|
+
key,
|
|
972
|
+
value === DEFAULT_DECISION && kept !== void 0 && kept !== DEFAULT_DECISION ? stored[key] : [value, now]
|
|
973
|
+
]);
|
|
974
|
+
entries.sort((a, b) => b[1][1] - a[1][1]);
|
|
975
|
+
entries.length = Math.min(entries.length, STORED_MAX_ENTRIES);
|
|
976
|
+
let json = JSON.stringify(Object.fromEntries(entries));
|
|
977
|
+
while (entries.length > 1 && encodeURIComponent(json).length > STORED_MAX_LENGTH) {
|
|
978
|
+
entries.pop();
|
|
979
|
+
json = JSON.stringify(Object.fromEntries(entries));
|
|
889
980
|
}
|
|
890
|
-
return
|
|
981
|
+
return json;
|
|
891
982
|
}
|
|
983
|
+
var DECISIONS_MAX_GROUPS = 40;
|
|
984
|
+
var SAFE_ID = /^[\w-]+$/;
|
|
892
985
|
function serializeDecisions(decisions) {
|
|
893
|
-
return [...decisions].
|
|
986
|
+
return [...decisions].filter(([group]) => SAFE_ID.test(group)).slice(0, DECISIONS_MAX_GROUPS).map(
|
|
987
|
+
([group, variants]) => `${group}:${variants.filter((v) => SAFE_ID.test(v)).join("|")}`
|
|
988
|
+
).join("~");
|
|
894
989
|
}
|
|
895
990
|
function forwardedHeaders(request, response) {
|
|
896
991
|
const overridden = response.headers.get("x-middleware-override-headers");
|
|
@@ -902,288 +997,448 @@ function forwardedHeaders(request, response) {
|
|
|
902
997
|
}
|
|
903
998
|
return getPreprHeadersFromHeaders(headers);
|
|
904
999
|
}
|
|
905
|
-
|
|
1000
|
+
var preprClientInternals = /* @__PURE__ */ new WeakMap();
|
|
1001
|
+
async function decideView(client, headers, page, timeoutMs = 1e3) {
|
|
1002
|
+
const init = { signal: AbortSignal.timeout(timeoutMs) };
|
|
906
1003
|
try {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1004
|
+
const decided = page.decision ? decisionsFrom(
|
|
1005
|
+
(await client.query(page.decision, page.variables, { headers, init })).data
|
|
1006
|
+
) : await client.decide(page.document, page.variables, { headers, init });
|
|
1007
|
+
return serializeDecisions(decided);
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (isFrameworkSignal(error)) throw error;
|
|
1010
|
+
return DEFAULT_DECISION;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/nextjs/proxy.ts
|
|
1015
|
+
import { NextResponse } from "next/server";
|
|
1016
|
+
function isRouterFetch(request) {
|
|
1017
|
+
return request.headers.has("rsc") || request.headers.has("next-url") || request.headers.get("sec-fetch-dest") === "empty";
|
|
1018
|
+
}
|
|
1019
|
+
var STATIC_PAGE_MS = 3e5;
|
|
1020
|
+
var STATIC_PAGES_MAX = 1e3;
|
|
1021
|
+
var staticPages = /* @__PURE__ */ new Map();
|
|
1022
|
+
function isPagePath(pathname) {
|
|
1023
|
+
return !pathname.startsWith("/_next/") && !pathname.startsWith("/api/") && !/\.[^/]*$/.test(pathname);
|
|
1024
|
+
}
|
|
1025
|
+
function createPreprProxy(options) {
|
|
1026
|
+
const internals = preprClientInternals.get(options.client);
|
|
1027
|
+
if (!internals) {
|
|
1028
|
+
throw new PreprError(
|
|
1029
|
+
"createPreprProxy needs a client made by createPreprClient from @preprio/toolkit/nextjs/graphql",
|
|
1030
|
+
"CONTEXT_ERROR",
|
|
1031
|
+
"createPreprProxy"
|
|
912
1032
|
);
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1033
|
+
}
|
|
1034
|
+
const { client } = internals;
|
|
1035
|
+
const preview = options.preview ?? false;
|
|
1036
|
+
const allowed = async (request) => {
|
|
1037
|
+
if (!options.onlyIf) return true;
|
|
1038
|
+
try {
|
|
1039
|
+
return await options.onlyIf(request);
|
|
1040
|
+
} catch {
|
|
1041
|
+
return false;
|
|
918
1042
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
return;
|
|
1043
|
+
};
|
|
1044
|
+
const lookup = (target) => {
|
|
1045
|
+
try {
|
|
1046
|
+
return options.page(target) ?? null;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
926
1049
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1050
|
+
};
|
|
1051
|
+
async function answerDecision(request, response, target) {
|
|
1052
|
+
let decision = DEFAULT_DECISION;
|
|
1053
|
+
const site = request.headers.get("sec-fetch-site");
|
|
1054
|
+
if (target && request.method === "GET" && (site === null || site === "same-origin") && await allowed(request)) {
|
|
1055
|
+
decision = await decideView(
|
|
1056
|
+
client,
|
|
1057
|
+
mergePreprHeaders(forwardedHeaders(request, response)),
|
|
1058
|
+
target,
|
|
1059
|
+
options.timeoutMs
|
|
1060
|
+
);
|
|
934
1061
|
}
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
for (const [group, variants] of decided) {
|
|
940
|
-
if (SAFE_ID.test(group)) {
|
|
941
|
-
next.set(
|
|
942
|
-
group,
|
|
943
|
-
variants.filter((v) => SAFE_ID.test(v))
|
|
944
|
-
);
|
|
1062
|
+
const answer = new NextResponse(decision, {
|
|
1063
|
+
headers: {
|
|
1064
|
+
"Cache-Control": "private, no-store",
|
|
1065
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
945
1066
|
}
|
|
1067
|
+
});
|
|
1068
|
+
for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
|
|
1069
|
+
return answer;
|
|
1070
|
+
}
|
|
1071
|
+
async function decidePageLoad(request, response, target) {
|
|
1072
|
+
const { basePath, pathname, search } = request.nextUrl;
|
|
1073
|
+
const key = basePath + pathname + search;
|
|
1074
|
+
const now = Date.now();
|
|
1075
|
+
if ((staticPages.get(key) ?? 0) > now) return response;
|
|
1076
|
+
const stored = readStoredDecisions(
|
|
1077
|
+
request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
|
|
1078
|
+
);
|
|
1079
|
+
if (!preview && usableDecision(stored, key, now) !== void 0)
|
|
1080
|
+
return response;
|
|
1081
|
+
if (!await allowed(request)) return response;
|
|
1082
|
+
const decision = await decideView(
|
|
1083
|
+
client,
|
|
1084
|
+
mergePreprHeaders(forwardedHeaders(request, response)),
|
|
1085
|
+
target,
|
|
1086
|
+
options.timeoutMs
|
|
1087
|
+
);
|
|
1088
|
+
if (decision === "") {
|
|
1089
|
+
if (staticPages.size >= STATIC_PAGES_MAX) staticPages.clear();
|
|
1090
|
+
staticPages.set(key, now + STATIC_PAGE_MS);
|
|
1091
|
+
return response;
|
|
946
1092
|
}
|
|
947
|
-
|
|
948
|
-
|
|
1093
|
+
response.cookies.set(
|
|
1094
|
+
PREPR_DECISIONS_COOKIE,
|
|
1095
|
+
writeStoredDecisions(stored, key, decision, now),
|
|
1096
|
+
{
|
|
1097
|
+
path: "/",
|
|
1098
|
+
maxAge: 3600,
|
|
1099
|
+
sameSite: "lax",
|
|
1100
|
+
secure: request.nextUrl.protocol === "https:"
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
return response;
|
|
1104
|
+
}
|
|
1105
|
+
return async (request) => {
|
|
1106
|
+
const rendersPage = (request.method === "GET" || request.method === "HEAD") && isPagePath(request.nextUrl.pathname);
|
|
1107
|
+
const target = rendersPage ? lookup(request.nextUrl) : null;
|
|
1108
|
+
const decisionRequest = request.headers.has(PREPR_DECIDE_HEADER);
|
|
1109
|
+
if (target && preview && !decisionRequest) {
|
|
1110
|
+
const draft = preprDraftRedirect(request, { preview });
|
|
1111
|
+
if (draft) return draft;
|
|
949
1112
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
sameSite: "lax",
|
|
954
|
-
// Read by the inline switch script before first paint.
|
|
955
|
-
httpOnly: false
|
|
1113
|
+
const response = createPreprMiddleware(request, {
|
|
1114
|
+
preview,
|
|
1115
|
+
features: options.features
|
|
956
1116
|
});
|
|
957
|
-
|
|
958
|
-
if (
|
|
959
|
-
|
|
1117
|
+
if (decisionRequest) return answerDecision(request, response, target);
|
|
1118
|
+
if (target && request.method === "GET" && !isRouterFetch(request))
|
|
1119
|
+
return decidePageLoad(request, response, target);
|
|
1120
|
+
return response;
|
|
1121
|
+
};
|
|
960
1122
|
}
|
|
961
1123
|
|
|
962
1124
|
// src/nextjs/stack.ts
|
|
963
1125
|
import {
|
|
964
1126
|
createElement,
|
|
965
|
-
Fragment
|
|
966
|
-
Suspense
|
|
1127
|
+
Fragment
|
|
967
1128
|
} from "react";
|
|
968
1129
|
|
|
969
|
-
// src/nextjs/
|
|
970
|
-
function
|
|
971
|
-
const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
|
|
972
|
-
const bucket = match ? match[1] : "A";
|
|
1130
|
+
// src/nextjs/decision-switch.ts
|
|
1131
|
+
function applyPreprDecisions(decision, options = {}) {
|
|
973
1132
|
const state = window;
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
raw = "";
|
|
1133
|
+
const key = location.pathname + location.search;
|
|
1134
|
+
const previous = state.__preprDecision;
|
|
1135
|
+
let value = decision;
|
|
1136
|
+
if (value !== void 0 && value !== "*" && options.refresh && previous && previous.key === key && previous.value !== "*") {
|
|
1137
|
+
const pairs = (from) => {
|
|
1138
|
+
const out = {};
|
|
1139
|
+
from.split("~").forEach((pair) => {
|
|
1140
|
+
const colon = pair.indexOf(":");
|
|
1141
|
+
if (colon > 0) out[pair.slice(0, colon)] = pair;
|
|
1142
|
+
});
|
|
1143
|
+
return out;
|
|
1144
|
+
};
|
|
1145
|
+
const before = pairs(previous.value);
|
|
1146
|
+
const after = pairs(value);
|
|
1147
|
+
document.querySelectorAll("[data-prepr-v][data-prepr-show]").forEach((element) => {
|
|
1148
|
+
const range = document.createRange();
|
|
1149
|
+
range.selectNodeContents(element);
|
|
1150
|
+
if (range.getBoundingClientRect().top >= window.innerHeight) return;
|
|
1151
|
+
(element.getAttribute("data-prepr-v") || "").split(" ").forEach((token) => {
|
|
1152
|
+
const group = token.slice(0, token.indexOf(":"));
|
|
1153
|
+
if (before[group]) after[group] = before[group];
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
value = Object.keys(after).map((group) => after[group]).join("~");
|
|
999
1157
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1158
|
+
if (value !== void 0) state.__preprDecision = { key, value };
|
|
1159
|
+
const current = state.__preprDecision;
|
|
1160
|
+
if (!current || current.key !== key) return false;
|
|
1161
|
+
document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
|
|
1162
|
+
marker.setAttribute("data-prepr-decided", "");
|
|
1163
|
+
});
|
|
1164
|
+
const defaults = current.value === "*";
|
|
1165
|
+
const chosen = {};
|
|
1166
|
+
current.value.split("~").forEach((pair) => {
|
|
1167
|
+
const colon = pair.indexOf(":");
|
|
1168
|
+
if (colon > 0)
|
|
1169
|
+
chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
|
|
1170
|
+
});
|
|
1171
|
+
document.querySelectorAll("[data-prepr-v]").forEach((element) => {
|
|
1172
|
+
const nested = element.hasAttribute("data-prepr-nested");
|
|
1173
|
+
const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
|
|
1174
|
+
const colon = token.indexOf(":");
|
|
1175
|
+
const variant = token.slice(colon + 1);
|
|
1176
|
+
const variants = chosen[token.slice(0, colon)];
|
|
1177
|
+
return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
|
|
1015
1178
|
});
|
|
1179
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
1180
|
+
else element.removeAttribute("data-prepr-show");
|
|
1016
1181
|
});
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1182
|
+
return true;
|
|
1183
|
+
}
|
|
1184
|
+
function requestPreprDecision(key, options = {}) {
|
|
1185
|
+
const fresh = 1e4;
|
|
1186
|
+
const keep = 18e5;
|
|
1187
|
+
const keepStatic = 36e5;
|
|
1188
|
+
const state = window;
|
|
1189
|
+
const asking = state.__preprAsking = state.__preprAsking || {};
|
|
1190
|
+
const read = () => {
|
|
1191
|
+
const all = {};
|
|
1192
|
+
try {
|
|
1193
|
+
const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
|
|
1194
|
+
const parsed = match ? JSON.parse(decodeURIComponent(match[1])) : null;
|
|
1195
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
1196
|
+
Object.keys(parsed).forEach((stored) => {
|
|
1197
|
+
const entry = parsed[stored];
|
|
1198
|
+
if (stored.charAt(0) === "/" && Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number")
|
|
1199
|
+
all[stored] = [entry[0], entry[1]];
|
|
1200
|
+
});
|
|
1201
|
+
} catch {
|
|
1022
1202
|
}
|
|
1023
|
-
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1203
|
+
return all;
|
|
1204
|
+
};
|
|
1205
|
+
const usable = (entry, now) => {
|
|
1206
|
+
if (!entry) return false;
|
|
1207
|
+
const age = now - entry[1];
|
|
1208
|
+
return age >= 0 && age < (entry[0] === "*" ? fresh : entry[0] === "" ? keepStatic : keep);
|
|
1209
|
+
};
|
|
1210
|
+
const store = (answer) => {
|
|
1211
|
+
const all = read();
|
|
1212
|
+
const now = Date.now();
|
|
1213
|
+
const kept = all[key];
|
|
1214
|
+
const entries = Object.keys(all).filter((stored) => stored !== key && usable(all[stored], now)).map((stored) => [stored, all[stored]]);
|
|
1215
|
+
entries.push([
|
|
1216
|
+
key,
|
|
1217
|
+
answer === "*" && kept && usable(kept, now) && kept[0] !== "*" ? kept : [answer, now]
|
|
1218
|
+
]);
|
|
1219
|
+
entries.sort((a, b) => b[1][1] - a[1][1]);
|
|
1220
|
+
entries.length = Math.min(entries.length, 20);
|
|
1221
|
+
const encode = () => {
|
|
1222
|
+
const out = {};
|
|
1223
|
+
entries.forEach((entry) => {
|
|
1224
|
+
out[entry[0]] = entry[1];
|
|
1225
|
+
});
|
|
1226
|
+
return encodeURIComponent(JSON.stringify(out));
|
|
1227
|
+
};
|
|
1228
|
+
let value = encode();
|
|
1229
|
+
while (entries.length > 1 && value.length > 3e3) {
|
|
1230
|
+
entries.pop();
|
|
1231
|
+
value = encode();
|
|
1232
|
+
}
|
|
1233
|
+
document.cookie = "prepr_decisions=" + value + "; path=/; max-age=3600; samesite=lax" + (location.protocol === "https:" ? "; secure" : "");
|
|
1234
|
+
};
|
|
1235
|
+
const ask = () => {
|
|
1236
|
+
if (!asking[key]) {
|
|
1237
|
+
asking[key] = fetch(key, {
|
|
1238
|
+
headers: { "Prepr-Decide": "1" },
|
|
1239
|
+
cache: "no-store",
|
|
1240
|
+
credentials: "same-origin",
|
|
1241
|
+
// The proxy gives up on Prepr after its own `timeoutMs`; this only
|
|
1242
|
+
// covers a proxy that never answers.
|
|
1243
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(6e3) : void 0
|
|
1244
|
+
}).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((answer) => {
|
|
1245
|
+
delete asking[key];
|
|
1246
|
+
try {
|
|
1247
|
+
store(answer);
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
return answer;
|
|
1251
|
+
});
|
|
1028
1252
|
}
|
|
1029
|
-
|
|
1253
|
+
return asking[key];
|
|
1254
|
+
};
|
|
1255
|
+
const last = read()[key];
|
|
1256
|
+
if (last && !(last[0] === "" && options.personalized) && usable(last, Date.now())) {
|
|
1257
|
+
const value = last[0];
|
|
1258
|
+
if (value !== "" && Date.now() - last[1] >= fresh) {
|
|
1259
|
+
ask().then((answer) => {
|
|
1260
|
+
if (answer !== "*" && answer !== value && options.onChange)
|
|
1261
|
+
options.onChange(answer);
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
return { result: value, value: Promise.resolve(value) };
|
|
1030
1265
|
}
|
|
1266
|
+
const pending = {
|
|
1267
|
+
value: ask()
|
|
1268
|
+
};
|
|
1269
|
+
pending.value.then((answer) => {
|
|
1270
|
+
pending.result = answer;
|
|
1271
|
+
});
|
|
1272
|
+
return pending;
|
|
1273
|
+
}
|
|
1274
|
+
function startPreprDecision() {
|
|
1275
|
+
const state = window;
|
|
1276
|
+
const key = location.pathname + location.search;
|
|
1277
|
+
const apply = (answer, refresh) => {
|
|
1278
|
+
if (location.pathname + location.search === key)
|
|
1279
|
+
state.__preprApply(answer, { refresh });
|
|
1280
|
+
};
|
|
1281
|
+
const pending = state.__preprRequest(key, {
|
|
1282
|
+
onChange: (answer) => apply(answer, true),
|
|
1283
|
+
personalized: true
|
|
1284
|
+
});
|
|
1285
|
+
if (pending.result !== void 0) apply(pending.result);
|
|
1286
|
+
else pending.value.then((answer) => apply(answer));
|
|
1031
1287
|
}
|
|
1032
1288
|
|
|
1033
1289
|
// src/nextjs/stack.ts
|
|
1034
|
-
import {
|
|
1035
|
-
var
|
|
1036
|
-
var AB_BUCKETS = ["A", "B"];
|
|
1037
|
-
var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
|
|
1038
|
-
var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
|
|
1039
|
-
var GROUP_ATTRIBUTE = "data-prepr-group";
|
|
1040
|
-
var VARIANT_ATTRIBUTE = "data-prepr-variant";
|
|
1290
|
+
import { PreprDecisionApplier, PreprVariantTracking } from "./components.js";
|
|
1291
|
+
var VARIANT_ATTRIBUTE = "data-prepr-v";
|
|
1041
1292
|
var DEFAULT_ATTRIBUTE = "data-prepr-default";
|
|
1042
|
-
var
|
|
1293
|
+
var NESTED_ATTRIBUTE = "data-prepr-nested";
|
|
1294
|
+
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}`;
|
|
1295
|
+
var NOSCRIPT_HTML = `<style>[${VARIANT_ATTRIBUTE}][${DEFAULT_ATTRIBUTE}]{display:contents!important;visibility:visible!important}</style>`;
|
|
1296
|
+
var DECISION_SCRIPT = `window.__preprApply=${applyPreprDecisions.toString()};window.__preprRequest=${requestPreprDecision.toString()};(${startPreprDecision.toString()})();`;
|
|
1297
|
+
var APPLY_SCRIPT = "__preprApply()";
|
|
1043
1298
|
function variantKeysOf(items) {
|
|
1044
1299
|
return items.flatMap((item) => {
|
|
1045
|
-
const key = preprVariantProps(item)["data-prepr-variant-key"];
|
|
1300
|
+
const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
|
|
1046
1301
|
return key ? [key] : [];
|
|
1047
1302
|
});
|
|
1048
1303
|
}
|
|
1049
|
-
function
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1304
|
+
function nestedKeyOf(value) {
|
|
1305
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1306
|
+
for (const child of Object.values(value)) {
|
|
1307
|
+
const items = Array.isArray(child) ? child : [child];
|
|
1308
|
+
for (const item of items) {
|
|
1309
|
+
const key = contextOf(item)?.variant_key ?? (item && nestedKeyOf(item));
|
|
1310
|
+
if (key) return key;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return void 0;
|
|
1314
|
+
}
|
|
1315
|
+
function renderItems(items, render, options) {
|
|
1316
|
+
const resolved = resolveVariants(items, {
|
|
1317
|
+
decisions: options.decisions,
|
|
1318
|
+
bucket: options.bucket
|
|
1319
|
+
});
|
|
1320
|
+
const content = render(resolved, {
|
|
1321
|
+
index: options.index,
|
|
1322
|
+
defaultVariant: options.defaultVariant
|
|
1323
|
+
});
|
|
1324
|
+
const variantKeys = options.track ? variantKeysOf(resolved) : [];
|
|
1325
|
+
return variantKeys.length > 0 ? createElement(PreprVariantTracking, { variantKeys }, content) : content;
|
|
1326
|
+
}
|
|
1327
|
+
function decidedGroups(items, bucket) {
|
|
1328
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1329
|
+
const walk = (value) => {
|
|
1330
|
+
if (Array.isArray(value)) return value.forEach(walk);
|
|
1331
|
+
if (!value || typeof value !== "object") return;
|
|
1332
|
+
const ctx = contextOf(value);
|
|
1333
|
+
const decided = ctx?.kind === "PERSONALIZATION" || ctx?.kind === "AB_TEST" && !bucket;
|
|
1334
|
+
if (decided && ctx.variant_id) {
|
|
1335
|
+
const group = groups.get(ctx.group_id) ?? {
|
|
1336
|
+
variants: /* @__PURE__ */ new Set(),
|
|
1337
|
+
fallback: ctx.kind === "AB_TEST" ? "A" : PREPR_DEFAULT_VARIANT
|
|
1338
|
+
};
|
|
1339
|
+
group.variants.add(ctx.variant_id);
|
|
1340
|
+
groups.set(ctx.group_id, group);
|
|
1069
1341
|
}
|
|
1070
|
-
|
|
1071
|
-
return content;
|
|
1342
|
+
Object.values(value).forEach(walk);
|
|
1072
1343
|
};
|
|
1073
|
-
|
|
1074
|
-
return
|
|
1075
|
-
}
|
|
1076
|
-
function
|
|
1077
|
-
const
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
)
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1344
|
+
walk(items);
|
|
1345
|
+
return groups;
|
|
1346
|
+
}
|
|
1347
|
+
function renderVariants(block, groups, render, options) {
|
|
1348
|
+
const top = block.key.startsWith("group:") ? block.key.slice("group:".length) : void 0;
|
|
1349
|
+
const nested = !(top !== void 0 && groups.size === 1);
|
|
1350
|
+
const choices = [...groups].map(([group, { variants, fallback }]) => {
|
|
1351
|
+
const ids = nested ? [fallback, ...variants] : [...variants];
|
|
1352
|
+
return [...new Set(ids)].map((variant) => [group, variant]);
|
|
1353
|
+
});
|
|
1354
|
+
const combinations = choices.reduce(
|
|
1355
|
+
(all, pairs) => all.flatMap((combo) => pairs.map((p) => [...combo, p])),
|
|
1356
|
+
[[]]
|
|
1357
|
+
);
|
|
1358
|
+
return combinations.map((combination) => {
|
|
1359
|
+
const pairs = combination.map(([group, variant]) => `${group}:${variant}`);
|
|
1360
|
+
const isDefault = combination.every(
|
|
1361
|
+
([group, variant]) => variant === groups.get(group).fallback
|
|
1362
|
+
);
|
|
1363
|
+
return createElement(
|
|
1088
1364
|
"div",
|
|
1089
1365
|
{
|
|
1090
|
-
key:
|
|
1091
|
-
[
|
|
1092
|
-
[
|
|
1093
|
-
...
|
|
1366
|
+
key: pairs.join(" "),
|
|
1367
|
+
[VARIANT_ATTRIBUTE]: pairs.join(" "),
|
|
1368
|
+
...isDefault ? { [DEFAULT_ATTRIBUTE]: "" } : {},
|
|
1369
|
+
...nested ? { [NESTED_ATTRIBUTE]: "" } : {},
|
|
1370
|
+
// The decision script marks the shown wrapper before hydration.
|
|
1371
|
+
suppressHydrationWarning: true
|
|
1094
1372
|
},
|
|
1095
|
-
|
|
1373
|
+
renderItems(block.items, render, {
|
|
1096
1374
|
bucket: options.bucket,
|
|
1097
|
-
decisions:
|
|
1098
|
-
abTest: false,
|
|
1375
|
+
decisions: new Map(combination.map(([g, v]) => [g, [v]])),
|
|
1099
1376
|
index: block.index,
|
|
1100
|
-
track: options.track
|
|
1377
|
+
track: options.track,
|
|
1378
|
+
defaultVariant: isDefault
|
|
1101
1379
|
})
|
|
1102
|
-
)
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
async function PersonalizedBlock({
|
|
1106
|
-
items,
|
|
1107
|
-
decisions,
|
|
1108
|
-
bucket,
|
|
1109
|
-
abTest,
|
|
1110
|
-
index,
|
|
1111
|
-
render,
|
|
1112
|
-
track
|
|
1113
|
-
}) {
|
|
1114
|
-
const decided = await decisions.catch((error) => {
|
|
1115
|
-
if (isFrameworkSignal(error)) throw error;
|
|
1116
|
-
return void 0;
|
|
1117
|
-
});
|
|
1118
|
-
return renderBuckets(items, render, {
|
|
1119
|
-
bucket,
|
|
1120
|
-
decisions: decided,
|
|
1121
|
-
abTest,
|
|
1122
|
-
index,
|
|
1123
|
-
track
|
|
1380
|
+
);
|
|
1124
1381
|
});
|
|
1125
1382
|
}
|
|
1126
1383
|
function PreprStack({
|
|
1127
1384
|
items,
|
|
1128
|
-
decisions,
|
|
1129
1385
|
render,
|
|
1130
1386
|
bucket,
|
|
1131
1387
|
nonce,
|
|
1132
|
-
trackVariants = false
|
|
1133
|
-
aboveTheFold = 0
|
|
1388
|
+
trackVariants = false
|
|
1134
1389
|
}) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const
|
|
1390
|
+
const blocks = splitStack(items).map((block) => ({
|
|
1391
|
+
...block,
|
|
1392
|
+
groups: decidedGroups(block.items, bucket)
|
|
1393
|
+
}));
|
|
1394
|
+
const varies = blocks.some((block) => block.groups.size > 0);
|
|
1140
1395
|
return createElement(
|
|
1141
1396
|
Fragment,
|
|
1142
1397
|
null,
|
|
1143
|
-
|
|
1398
|
+
varies && createElement(
|
|
1144
1399
|
Fragment,
|
|
1145
|
-
{ key: "prepr-
|
|
1400
|
+
{ key: "prepr-switch" },
|
|
1146
1401
|
createElement("style", {
|
|
1147
1402
|
nonce,
|
|
1148
|
-
dangerouslySetInnerHTML: {
|
|
1149
|
-
|
|
1150
|
-
|
|
1403
|
+
dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
|
|
1404
|
+
}),
|
|
1405
|
+
createElement("noscript", {
|
|
1406
|
+
dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
|
|
1151
1407
|
}),
|
|
1152
1408
|
createElement("script", {
|
|
1153
1409
|
nonce,
|
|
1154
|
-
dangerouslySetInnerHTML: { __html:
|
|
1410
|
+
dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
|
|
1155
1411
|
}),
|
|
1156
|
-
createElement(
|
|
1412
|
+
createElement(PreprDecisionApplier),
|
|
1413
|
+
// Marked decided by the decision switch; never laid out.
|
|
1414
|
+
createElement("template", {
|
|
1415
|
+
"data-prepr-decision": "",
|
|
1416
|
+
suppressHydrationWarning: true
|
|
1417
|
+
})
|
|
1157
1418
|
),
|
|
1158
1419
|
...blocks.map((block) => {
|
|
1159
|
-
if (
|
|
1420
|
+
if (block.groups.size === 0) {
|
|
1160
1421
|
return createElement(
|
|
1161
1422
|
Fragment,
|
|
1162
1423
|
{ key: block.key },
|
|
1163
|
-
|
|
1424
|
+
renderItems(block.items, render, {
|
|
1425
|
+
bucket,
|
|
1426
|
+
index: block.index,
|
|
1427
|
+
track: trackVariants,
|
|
1428
|
+
defaultVariant: true
|
|
1429
|
+
})
|
|
1164
1430
|
);
|
|
1165
1431
|
}
|
|
1166
|
-
const fallback = renderBuckets(block.items, render, {
|
|
1167
|
-
bucket,
|
|
1168
|
-
abTest: block.abTest,
|
|
1169
|
-
index: block.index,
|
|
1170
|
-
track: trackVariants && !block.personalized,
|
|
1171
|
-
placeholder: block.personalized
|
|
1172
|
-
});
|
|
1173
|
-
if (!block.personalized) {
|
|
1174
|
-
return createElement(Fragment, { key: block.key }, fallback);
|
|
1175
|
-
}
|
|
1176
1432
|
return createElement(
|
|
1177
|
-
|
|
1178
|
-
{ key: block.key
|
|
1179
|
-
|
|
1180
|
-
items: block.items,
|
|
1181
|
-
decisions,
|
|
1433
|
+
Fragment,
|
|
1434
|
+
{ key: block.key },
|
|
1435
|
+
renderVariants(block, block.groups, render, {
|
|
1182
1436
|
bucket,
|
|
1183
|
-
abTest: block.abTest,
|
|
1184
|
-
index: block.index,
|
|
1185
|
-
render,
|
|
1186
1437
|
track: trackVariants
|
|
1438
|
+
}),
|
|
1439
|
+
createElement("script", {
|
|
1440
|
+
nonce,
|
|
1441
|
+
dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
|
|
1187
1442
|
})
|
|
1188
1443
|
);
|
|
1189
1444
|
})
|
|
@@ -1195,33 +1450,59 @@ function createPreprClient2(options) {
|
|
|
1195
1450
|
const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
|
|
1196
1451
|
const client = createPreprClient({
|
|
1197
1452
|
// The schema introspection goes through the Data Cache: shared across
|
|
1198
|
-
// server instances, refreshed hourly and by
|
|
1453
|
+
// server instances, refreshed hourly and by a `schema.changed` webhook.
|
|
1199
1454
|
schemaInit: {
|
|
1200
|
-
next: { tags: cacheTags, revalidate: 3600 }
|
|
1455
|
+
next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
|
|
1201
1456
|
},
|
|
1202
1457
|
...coreOptions
|
|
1203
1458
|
});
|
|
1204
|
-
const
|
|
1205
|
-
return {
|
|
1459
|
+
const nextClient = {
|
|
1206
1460
|
async query(document2, variables, queryOptions = {}) {
|
|
1207
|
-
const { personalize = false, ...rest } = queryOptions;
|
|
1208
|
-
const init = rest.init;
|
|
1209
|
-
|
|
1461
|
+
const { personalize = false, tagItems = false, ...rest } = queryOptions;
|
|
1462
|
+
const { next, ...init } = rest.init ?? {};
|
|
1463
|
+
if (tagItems && typeof nextCache.cacheTag !== "function") {
|
|
1464
|
+
throw new Error("tagItems requires Next.js 16 or later.");
|
|
1465
|
+
}
|
|
1466
|
+
const options2 = {
|
|
1210
1467
|
...rest,
|
|
1211
|
-
|
|
1468
|
+
// With tagItems, `next` is dropped too: tags or revalidate alongside
|
|
1469
|
+
// `no-store` are a conflict Next.js resolves by ignoring the store.
|
|
1470
|
+
// Otherwise tags are attached unconditionally; the core client drops
|
|
1471
|
+
// them again whenever the request ends up `no-store` (personalized,
|
|
1472
|
+
// per-request headers, or preview).
|
|
1473
|
+
init: tagItems ? { ...init, cache: "no-store" } : {
|
|
1212
1474
|
...init,
|
|
1213
1475
|
next: {
|
|
1214
|
-
tags: cacheTags,
|
|
1215
1476
|
...revalidate === void 0 ? {} : { revalidate },
|
|
1216
|
-
...
|
|
1477
|
+
...next,
|
|
1478
|
+
// Merged, so a per-query tag never drops the client's own,
|
|
1479
|
+
// which the publish webhook relies on.
|
|
1480
|
+
tags: [...cacheTags, ...next?.tags ?? []]
|
|
1217
1481
|
}
|
|
1218
1482
|
}
|
|
1219
1483
|
};
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1484
|
+
if (personalize) {
|
|
1485
|
+
options2.headers = mergePreprHeaders(
|
|
1486
|
+
await getPreprHeaders(),
|
|
1487
|
+
rest.headers
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
const result = await client.query(document2, variables, options2);
|
|
1491
|
+
if (tagItems) {
|
|
1492
|
+
let modelTags;
|
|
1493
|
+
try {
|
|
1494
|
+
modelTags = preprModelTagsFor(
|
|
1495
|
+
documentOf(document2),
|
|
1496
|
+
result.data,
|
|
1497
|
+
await clientSchemas.get(client)()
|
|
1498
|
+
);
|
|
1499
|
+
} catch {
|
|
1500
|
+
modelTags = [PREPR_ANY_ITEM_TAG];
|
|
1501
|
+
}
|
|
1502
|
+
nextCache.cacheTag(...cacheTags, ...next?.tags ?? [], ...modelTags);
|
|
1503
|
+
nextCache.cacheTag(...preprCacheTags(result.data));
|
|
1504
|
+
}
|
|
1505
|
+
return result;
|
|
1225
1506
|
},
|
|
1226
1507
|
async decide(document2, variables, queryOptions = {}) {
|
|
1227
1508
|
return client.decide(document2, variables, {
|
|
@@ -1232,27 +1513,23 @@ function createPreprClient2(options) {
|
|
|
1232
1513
|
)
|
|
1233
1514
|
});
|
|
1234
1515
|
},
|
|
1235
|
-
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1236
|
-
resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
|
|
1237
|
-
client,
|
|
1238
|
-
seenGroups,
|
|
1239
|
-
request,
|
|
1240
|
-
response,
|
|
1241
|
-
document2,
|
|
1242
|
-
variables,
|
|
1243
|
-
options2
|
|
1244
|
-
)
|
|
1516
|
+
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1245
1517
|
};
|
|
1518
|
+
preprClientInternals.set(nextClient, { client });
|
|
1519
|
+
return nextClient;
|
|
1246
1520
|
}
|
|
1247
1521
|
export {
|
|
1248
|
-
|
|
1522
|
+
PREPR_ANY_ITEM_TAG,
|
|
1249
1523
|
PREPR_DEFAULT_VARIANT,
|
|
1250
1524
|
PreprError,
|
|
1251
1525
|
PreprStack,
|
|
1252
1526
|
createPreprClient2 as createPreprClient,
|
|
1527
|
+
createPreprProxy,
|
|
1253
1528
|
decisionsFrom,
|
|
1254
1529
|
isPersonalizedResponse,
|
|
1255
1530
|
loadPreprSchema,
|
|
1531
|
+
preprCacheTags,
|
|
1532
|
+
preprModelTag,
|
|
1256
1533
|
preprVariantProps,
|
|
1257
1534
|
resolveVariants,
|
|
1258
1535
|
splitStack,
|