@preprio/toolkit 0.6.0-alpha.0 → 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 +268 -59
- package/dist/astro/{chunk-3UVUDDNY.js → chunk-CG2QBHNU.js} +1 -1
- package/dist/astro/graphql.cjs +154 -59
- package/dist/astro/graphql.d.cts +75 -6
- package/dist/astro/graphql.d.ts +75 -6
- package/dist/astro/graphql.js +150 -59
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/chunk-3NCEVJJ3.js +10 -0
- package/dist/{chunk-BJ2LBLWO.js → chunk-LIDUJWWJ.js} +6 -4
- package/dist/chunk-M3RBYJ5J.js +499 -0
- package/dist/{chunk-DGZA7KX7.js → chunk-S5RWVMDJ.js} +0 -24
- package/dist/{chunk-YR7P4IWE.js → chunk-SF6TN4JR.js} +1 -9
- package/dist/chunk-TN6SNAR2.js +26 -0
- package/dist/graphql/codegen.cjs +557 -0
- package/dist/graphql/codegen.d.cts +23 -0
- package/dist/graphql/codegen.d.ts +23 -0
- package/dist/graphql/codegen.js +99 -0
- package/dist/graphql.cjs +185 -59
- package/dist/graphql.d.cts +110 -6
- package/dist/graphql.d.ts +110 -6
- package/dist/graphql.js +91 -430
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -6
- 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 +289 -73
- package/dist/nextjs/graphql.cjs +952 -315
- package/dist/nextjs/graphql.d.cts +225 -83
- package/dist/nextjs/graphql.d.ts +225 -83
- package/dist/nextjs/graphql.js +634 -314
- 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-P6ENNAEE.js → chunk-FMC3DP7N.js} +1 -1
- package/dist/nuxt/graphql.cjs +154 -59
- package/dist/nuxt/graphql.d.cts +75 -6
- package/dist/nuxt/graphql.d.ts +75 -6
- package/dist/nuxt/graphql.js +150 -59
- 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-FSARVMG5.js → chunk-AJPKBAHO.js} +1 -1
- package/dist/sveltekit/graphql.cjs +154 -59
- package/dist/sveltekit/graphql.d.cts +75 -6
- package/dist/sveltekit/graphql.d.ts +75 -6
- package/dist/sveltekit/graphql.js +150 -59
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +6 -1
- package/dist/nextjs/chunk-U4HXENF7.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) {
|
|
@@ -483,6 +540,63 @@ function toVariantsDocument(document2, schema) {
|
|
|
483
540
|
};
|
|
484
541
|
}
|
|
485
542
|
|
|
543
|
+
// src/core/graphql/document.ts
|
|
544
|
+
import { print } from "graphql/language/printer.js";
|
|
545
|
+
import { stripIgnoredCharacters } from "graphql/utilities/stripIgnoredCharacters.js";
|
|
546
|
+
var printed = /* @__PURE__ */ new WeakMap();
|
|
547
|
+
function isDocumentNode(value) {
|
|
548
|
+
return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
|
|
549
|
+
}
|
|
550
|
+
function invalidDocument(originalError) {
|
|
551
|
+
return new PreprError(
|
|
552
|
+
"Expected a GraphQL query string, TypedDocumentString or DocumentNode",
|
|
553
|
+
"INVALID_DOCUMENT",
|
|
554
|
+
"createPreprClient",
|
|
555
|
+
originalError
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
var minified = /* @__PURE__ */ new Map();
|
|
559
|
+
var MINIFIED_CACHE_LIMIT = 500;
|
|
560
|
+
function minify(query) {
|
|
561
|
+
const cached = minified.get(query);
|
|
562
|
+
if (cached !== void 0) return cached;
|
|
563
|
+
let result = query;
|
|
564
|
+
try {
|
|
565
|
+
result = stripIgnoredCharacters(query);
|
|
566
|
+
} catch {
|
|
567
|
+
}
|
|
568
|
+
if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
function resolveQuery(document2) {
|
|
572
|
+
if (typeof document2 === "string") return minify(document2);
|
|
573
|
+
if (document2 instanceof String) return minify(document2.toString());
|
|
574
|
+
if (!isDocumentNode(document2)) throw invalidDocument();
|
|
575
|
+
let query = printed.get(document2);
|
|
576
|
+
if (query === void 0) {
|
|
577
|
+
try {
|
|
578
|
+
query = minify(print(document2));
|
|
579
|
+
} catch (error) {
|
|
580
|
+
throw invalidDocument(error instanceof Error ? error : void 0);
|
|
581
|
+
}
|
|
582
|
+
printed.set(document2, query);
|
|
583
|
+
}
|
|
584
|
+
return query;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/core/variants/queries.ts
|
|
588
|
+
function toVariantsQuery(document2, schema) {
|
|
589
|
+
return resolveQuery(toVariantsDocument(document2, schema));
|
|
590
|
+
}
|
|
591
|
+
function toDecisionQuery(document2, schema, options = {}) {
|
|
592
|
+
const paths = options.from === void 0 ? void 0 : personalizationPaths(options.from);
|
|
593
|
+
if (paths && paths.size === 0) return null;
|
|
594
|
+
const decision = toDecisionDocument(document2, schema, paths, {
|
|
595
|
+
topLevelOnly: !paths && options.topLevelOnly
|
|
596
|
+
});
|
|
597
|
+
return decision ? resolveQuery(decision) : null;
|
|
598
|
+
}
|
|
599
|
+
|
|
486
600
|
// src/core/graphql/transport.ts
|
|
487
601
|
import { VERCEL_STEGA_REGEX } from "@vercel/stega";
|
|
488
602
|
|
|
@@ -535,50 +649,6 @@ function stegaClean(text) {
|
|
|
535
649
|
return vercelStegaClean(text);
|
|
536
650
|
}
|
|
537
651
|
|
|
538
|
-
// src/core/graphql/document.ts
|
|
539
|
-
import { print } from "graphql/language/printer.js";
|
|
540
|
-
import { stripIgnoredCharacters } from "graphql/utilities/stripIgnoredCharacters.js";
|
|
541
|
-
var printed = /* @__PURE__ */ new WeakMap();
|
|
542
|
-
function isDocumentNode(value) {
|
|
543
|
-
return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
|
|
544
|
-
}
|
|
545
|
-
function invalidDocument(originalError) {
|
|
546
|
-
return new PreprError(
|
|
547
|
-
"Expected a GraphQL query string, TypedDocumentString or DocumentNode",
|
|
548
|
-
"INVALID_DOCUMENT",
|
|
549
|
-
"createPreprClient",
|
|
550
|
-
originalError
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
var minified = /* @__PURE__ */ new Map();
|
|
554
|
-
var MINIFIED_CACHE_LIMIT = 500;
|
|
555
|
-
function minify(query) {
|
|
556
|
-
const cached = minified.get(query);
|
|
557
|
-
if (cached !== void 0) return cached;
|
|
558
|
-
let result = query;
|
|
559
|
-
try {
|
|
560
|
-
result = stripIgnoredCharacters(query);
|
|
561
|
-
} catch {
|
|
562
|
-
}
|
|
563
|
-
if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
|
|
564
|
-
return result;
|
|
565
|
-
}
|
|
566
|
-
function resolveQuery(document2) {
|
|
567
|
-
if (typeof document2 === "string") return minify(document2);
|
|
568
|
-
if (document2 instanceof String) return minify(document2.toString());
|
|
569
|
-
if (!isDocumentNode(document2)) throw invalidDocument();
|
|
570
|
-
let query = printed.get(document2);
|
|
571
|
-
if (query === void 0) {
|
|
572
|
-
try {
|
|
573
|
-
query = minify(print(document2));
|
|
574
|
-
} catch (error) {
|
|
575
|
-
throw invalidDocument(error instanceof Error ? error : void 0);
|
|
576
|
-
}
|
|
577
|
-
printed.set(document2, query);
|
|
578
|
-
}
|
|
579
|
-
return query;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
652
|
// src/core/graphql/transport.ts
|
|
583
653
|
var OPERATION_NAME = /\b(?:query|mutation|subscription)\s+([_A-Za-z][_0-9A-Za-z]*)/;
|
|
584
654
|
var STEGA_RAW = new RegExp(
|
|
@@ -757,6 +827,7 @@ function createVaryProbe(run, options) {
|
|
|
757
827
|
}
|
|
758
828
|
|
|
759
829
|
// src/core/graphql/client.ts
|
|
830
|
+
var clientSchemas = /* @__PURE__ */ new WeakMap();
|
|
760
831
|
function isParseableUrl(url) {
|
|
761
832
|
try {
|
|
762
833
|
new URL(url);
|
|
@@ -765,16 +836,18 @@ function isParseableUrl(url) {
|
|
|
765
836
|
return false;
|
|
766
837
|
}
|
|
767
838
|
}
|
|
768
|
-
function
|
|
769
|
-
const { url } = options;
|
|
839
|
+
function assertPreprUrl(url, source) {
|
|
770
840
|
validatePreprToken(url);
|
|
771
841
|
if (!isParseableUrl(url)) {
|
|
772
842
|
throw new PreprError(
|
|
773
843
|
"Prepr GraphQL URL is not a valid URL",
|
|
774
844
|
"INVALID_TOKEN",
|
|
775
|
-
|
|
845
|
+
source
|
|
776
846
|
);
|
|
777
847
|
}
|
|
848
|
+
}
|
|
849
|
+
function createPreprClient(options) {
|
|
850
|
+
assertPreprUrl(options.url, "createPreprClient");
|
|
778
851
|
const run = createTransport(options);
|
|
779
852
|
const probeVary = createVaryProbe(run, options);
|
|
780
853
|
let schema;
|
|
@@ -791,7 +864,7 @@ function createPreprClient(options) {
|
|
|
791
864
|
));
|
|
792
865
|
const variantsDocuments = /* @__PURE__ */ new Map();
|
|
793
866
|
const decisionDocuments = /* @__PURE__ */ new Map();
|
|
794
|
-
|
|
867
|
+
const client = {
|
|
795
868
|
async query(document2, variables, queryOptions = {}) {
|
|
796
869
|
const { variants, ...rest } = queryOptions;
|
|
797
870
|
if (!variants) return run(document2, variables, rest);
|
|
@@ -803,10 +876,10 @@ function createPreprClient(options) {
|
|
|
803
876
|
return run(derived, variables, rest);
|
|
804
877
|
},
|
|
805
878
|
async decide(document2, variables, queryOptions = {}) {
|
|
806
|
-
const { from,
|
|
879
|
+
const { from, ...rest } = queryOptions;
|
|
807
880
|
const paths = from === void 0 ? void 0 : personalizationPaths(from);
|
|
808
881
|
if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
|
|
809
|
-
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("|") : "*";
|
|
810
883
|
let byPaths = decisionDocuments.get(document2);
|
|
811
884
|
if (!byPaths) {
|
|
812
885
|
byPaths = /* @__PURE__ */ new Map();
|
|
@@ -814,9 +887,7 @@ function createPreprClient(options) {
|
|
|
814
887
|
}
|
|
815
888
|
let derived = byPaths.get(signature);
|
|
816
889
|
if (derived === void 0) {
|
|
817
|
-
derived = toDecisionDocument(document2, await loadSchema(), paths
|
|
818
|
-
topLevelOnly: !paths && topLevelOnly
|
|
819
|
-
});
|
|
890
|
+
derived = toDecisionDocument(document2, await loadSchema(), paths);
|
|
820
891
|
byPaths.set(signature, derived);
|
|
821
892
|
}
|
|
822
893
|
if (!derived) return /* @__PURE__ */ new Map();
|
|
@@ -830,28 +901,91 @@ function createPreprClient(options) {
|
|
|
830
901
|
(vary) => vary === null || SEGMENTS_VARY.test(vary)
|
|
831
902
|
)
|
|
832
903
|
};
|
|
904
|
+
clientSchemas.set(client, loadSchema);
|
|
905
|
+
return client;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/core/graphql/schema-loader.ts
|
|
909
|
+
var schemas = /* @__PURE__ */ new Map();
|
|
910
|
+
async function loadPreprSchema(url, options = {}) {
|
|
911
|
+
assertPreprUrl(url, "loadPreprSchema");
|
|
912
|
+
let schema = schemas.get(url);
|
|
913
|
+
if (!schema) {
|
|
914
|
+
const run = createTransport({ url, fetch: options.fetch, stega: "none" });
|
|
915
|
+
schema = run(
|
|
916
|
+
PREPR_SCHEMA_QUERY
|
|
917
|
+
).then((result) => toSchemaInfo(result.data)).catch((error) => {
|
|
918
|
+
schemas.delete(url);
|
|
919
|
+
if (error instanceof PreprError) throw error;
|
|
920
|
+
throw new PreprError(
|
|
921
|
+
"Prepr schema response is not an introspection result",
|
|
922
|
+
"INVALID_RESPONSE",
|
|
923
|
+
"loadPreprSchema",
|
|
924
|
+
error instanceof Error ? error : void 0
|
|
925
|
+
);
|
|
926
|
+
});
|
|
927
|
+
schemas.set(url, schema);
|
|
928
|
+
}
|
|
929
|
+
return schema;
|
|
833
930
|
}
|
|
834
931
|
|
|
835
932
|
// src/nextjs/decisions.ts
|
|
933
|
+
var PREPR_DECIDE_HEADER = "Prepr-Decide";
|
|
934
|
+
var DEFAULT_DECISION = "*";
|
|
836
935
|
var PREPR_DECISIONS_COOKIE = "prepr_decisions";
|
|
837
|
-
var
|
|
838
|
-
var
|
|
839
|
-
var
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
)
|
|
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 {
|
|
850
954
|
}
|
|
851
|
-
return
|
|
955
|
+
return stored;
|
|
852
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));
|
|
980
|
+
}
|
|
981
|
+
return json;
|
|
982
|
+
}
|
|
983
|
+
var DECISIONS_MAX_GROUPS = 40;
|
|
984
|
+
var SAFE_ID = /^[\w-]+$/;
|
|
853
985
|
function serializeDecisions(decisions) {
|
|
854
|
-
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("~");
|
|
855
989
|
}
|
|
856
990
|
function forwardedHeaders(request, response) {
|
|
857
991
|
const overridden = response.headers.get("x-middleware-override-headers");
|
|
@@ -863,288 +997,448 @@ function forwardedHeaders(request, response) {
|
|
|
863
997
|
}
|
|
864
998
|
return getPreprHeadersFromHeaders(headers);
|
|
865
999
|
}
|
|
866
|
-
|
|
1000
|
+
var preprClientInternals = /* @__PURE__ */ new WeakMap();
|
|
1001
|
+
async function decideView(client, headers, page, timeoutMs = 1e3) {
|
|
1002
|
+
const init = { signal: AbortSignal.timeout(timeoutMs) };
|
|
867
1003
|
try {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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"
|
|
873
1032
|
);
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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;
|
|
879
1042
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
(
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return;
|
|
1043
|
+
};
|
|
1044
|
+
const lookup = (target) => {
|
|
1045
|
+
try {
|
|
1046
|
+
return options.page(target) ?? null;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
887
1049
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
+
);
|
|
895
1061
|
}
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
for (const [group, variants] of decided) {
|
|
901
|
-
if (SAFE_ID.test(group)) {
|
|
902
|
-
next.set(
|
|
903
|
-
group,
|
|
904
|
-
variants.filter((v) => SAFE_ID.test(v))
|
|
905
|
-
);
|
|
1062
|
+
const answer = new NextResponse(decision, {
|
|
1063
|
+
headers: {
|
|
1064
|
+
"Cache-Control": "private, no-store",
|
|
1065
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
906
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;
|
|
907
1092
|
}
|
|
908
|
-
|
|
909
|
-
|
|
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;
|
|
910
1112
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
sameSite: "lax",
|
|
915
|
-
// Read by the inline switch script before first paint.
|
|
916
|
-
httpOnly: false
|
|
1113
|
+
const response = createPreprMiddleware(request, {
|
|
1114
|
+
preview,
|
|
1115
|
+
features: options.features
|
|
917
1116
|
});
|
|
918
|
-
|
|
919
|
-
if (
|
|
920
|
-
|
|
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
|
+
};
|
|
921
1122
|
}
|
|
922
1123
|
|
|
923
1124
|
// src/nextjs/stack.ts
|
|
924
1125
|
import {
|
|
925
1126
|
createElement,
|
|
926
|
-
Fragment
|
|
927
|
-
Suspense
|
|
1127
|
+
Fragment
|
|
928
1128
|
} from "react";
|
|
929
1129
|
|
|
930
|
-
// src/nextjs/
|
|
931
|
-
function
|
|
932
|
-
const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
|
|
933
|
-
const bucket = match ? match[1] : "A";
|
|
1130
|
+
// src/nextjs/decision-switch.ts
|
|
1131
|
+
function applyPreprDecisions(decision, options = {}) {
|
|
934
1132
|
const state = window;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
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("~");
|
|
960
1157
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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);
|
|
976
1178
|
});
|
|
1179
|
+
if (show) element.setAttribute("data-prepr-show", "");
|
|
1180
|
+
else element.removeAttribute("data-prepr-show");
|
|
977
1181
|
});
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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 {
|
|
983
1202
|
}
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
+
});
|
|
1252
|
+
}
|
|
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
|
+
});
|
|
989
1263
|
}
|
|
990
|
-
|
|
1264
|
+
return { result: value, value: Promise.resolve(value) };
|
|
991
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));
|
|
992
1287
|
}
|
|
993
1288
|
|
|
994
1289
|
// src/nextjs/stack.ts
|
|
995
|
-
import {
|
|
996
|
-
var
|
|
997
|
-
var AB_BUCKETS = ["A", "B"];
|
|
998
|
-
var AB_BASE_CSS = `[${AB_ATTRIBUTE}]{display:contents}[${AB_ATTRIBUTE}]:not([${AB_ATTRIBUTE}="A"]){display:none}`;
|
|
999
|
-
var AB_SWITCH_SCRIPT = `(${applyPreprAbBucket.toString()})();(${applyPreprDecisions.toString()})()`;
|
|
1000
|
-
var GROUP_ATTRIBUTE = "data-prepr-group";
|
|
1001
|
-
var VARIANT_ATTRIBUTE = "data-prepr-variant";
|
|
1290
|
+
import { PreprDecisionApplier, PreprVariantTracking } from "./components.js";
|
|
1291
|
+
var VARIANT_ATTRIBUTE = "data-prepr-v";
|
|
1002
1292
|
var DEFAULT_ATTRIBUTE = "data-prepr-default";
|
|
1003
|
-
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()";
|
|
1004
1298
|
function variantKeysOf(items) {
|
|
1005
1299
|
return items.flatMap((item) => {
|
|
1006
|
-
const key = preprVariantProps(item)["data-prepr-variant-key"];
|
|
1300
|
+
const key = preprVariantProps(item)["data-prepr-variant-key"] ?? nestedKeyOf(item);
|
|
1007
1301
|
return key ? [key] : [];
|
|
1008
1302
|
});
|
|
1009
1303
|
}
|
|
1010
|
-
function
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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);
|
|
1030
1341
|
}
|
|
1031
|
-
|
|
1032
|
-
return content;
|
|
1342
|
+
Object.values(value).forEach(walk);
|
|
1033
1343
|
};
|
|
1034
|
-
|
|
1035
|
-
return
|
|
1036
|
-
}
|
|
1037
|
-
function
|
|
1038
|
-
const
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
)
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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(
|
|
1049
1364
|
"div",
|
|
1050
1365
|
{
|
|
1051
|
-
key:
|
|
1052
|
-
[
|
|
1053
|
-
[
|
|
1054
|
-
...
|
|
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
|
|
1055
1372
|
},
|
|
1056
|
-
|
|
1373
|
+
renderItems(block.items, render, {
|
|
1057
1374
|
bucket: options.bucket,
|
|
1058
|
-
decisions:
|
|
1059
|
-
abTest: false,
|
|
1375
|
+
decisions: new Map(combination.map(([g, v]) => [g, [v]])),
|
|
1060
1376
|
index: block.index,
|
|
1061
|
-
track: options.track
|
|
1377
|
+
track: options.track,
|
|
1378
|
+
defaultVariant: isDefault
|
|
1062
1379
|
})
|
|
1063
|
-
)
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
async function PersonalizedBlock({
|
|
1067
|
-
items,
|
|
1068
|
-
decisions,
|
|
1069
|
-
bucket,
|
|
1070
|
-
abTest,
|
|
1071
|
-
index,
|
|
1072
|
-
render,
|
|
1073
|
-
track
|
|
1074
|
-
}) {
|
|
1075
|
-
const decided = await decisions.catch((error) => {
|
|
1076
|
-
if (isFrameworkSignal(error)) throw error;
|
|
1077
|
-
return void 0;
|
|
1078
|
-
});
|
|
1079
|
-
return renderBuckets(items, render, {
|
|
1080
|
-
bucket,
|
|
1081
|
-
decisions: decided,
|
|
1082
|
-
abTest,
|
|
1083
|
-
index,
|
|
1084
|
-
track
|
|
1380
|
+
);
|
|
1085
1381
|
});
|
|
1086
1382
|
}
|
|
1087
1383
|
function PreprStack({
|
|
1088
1384
|
items,
|
|
1089
|
-
decisions,
|
|
1090
1385
|
render,
|
|
1091
1386
|
bucket,
|
|
1092
1387
|
nonce,
|
|
1093
|
-
trackVariants = false
|
|
1094
|
-
aboveTheFold = 0
|
|
1388
|
+
trackVariants = false
|
|
1095
1389
|
}) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
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);
|
|
1101
1395
|
return createElement(
|
|
1102
1396
|
Fragment,
|
|
1103
1397
|
null,
|
|
1104
|
-
|
|
1398
|
+
varies && createElement(
|
|
1105
1399
|
Fragment,
|
|
1106
|
-
{ key: "prepr-
|
|
1400
|
+
{ key: "prepr-switch" },
|
|
1107
1401
|
createElement("style", {
|
|
1108
1402
|
nonce,
|
|
1109
|
-
dangerouslySetInnerHTML: {
|
|
1110
|
-
|
|
1111
|
-
|
|
1403
|
+
dangerouslySetInnerHTML: { __html: GROUP_BASE_CSS }
|
|
1404
|
+
}),
|
|
1405
|
+
createElement("noscript", {
|
|
1406
|
+
dangerouslySetInnerHTML: { __html: NOSCRIPT_HTML }
|
|
1112
1407
|
}),
|
|
1113
1408
|
createElement("script", {
|
|
1114
1409
|
nonce,
|
|
1115
|
-
dangerouslySetInnerHTML: { __html:
|
|
1410
|
+
dangerouslySetInnerHTML: { __html: DECISION_SCRIPT }
|
|
1116
1411
|
}),
|
|
1117
|
-
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
|
+
})
|
|
1118
1418
|
),
|
|
1119
1419
|
...blocks.map((block) => {
|
|
1120
|
-
if (
|
|
1420
|
+
if (block.groups.size === 0) {
|
|
1121
1421
|
return createElement(
|
|
1122
1422
|
Fragment,
|
|
1123
1423
|
{ key: block.key },
|
|
1124
|
-
|
|
1424
|
+
renderItems(block.items, render, {
|
|
1425
|
+
bucket,
|
|
1426
|
+
index: block.index,
|
|
1427
|
+
track: trackVariants,
|
|
1428
|
+
defaultVariant: true
|
|
1429
|
+
})
|
|
1125
1430
|
);
|
|
1126
1431
|
}
|
|
1127
|
-
const fallback = renderBuckets(block.items, render, {
|
|
1128
|
-
bucket,
|
|
1129
|
-
abTest: block.abTest,
|
|
1130
|
-
index: block.index,
|
|
1131
|
-
track: trackVariants && !block.personalized,
|
|
1132
|
-
placeholder: block.personalized
|
|
1133
|
-
});
|
|
1134
|
-
if (!block.personalized) {
|
|
1135
|
-
return createElement(Fragment, { key: block.key }, fallback);
|
|
1136
|
-
}
|
|
1137
1432
|
return createElement(
|
|
1138
|
-
|
|
1139
|
-
{ key: block.key
|
|
1140
|
-
|
|
1141
|
-
items: block.items,
|
|
1142
|
-
decisions,
|
|
1433
|
+
Fragment,
|
|
1434
|
+
{ key: block.key },
|
|
1435
|
+
renderVariants(block, block.groups, render, {
|
|
1143
1436
|
bucket,
|
|
1144
|
-
abTest: block.abTest,
|
|
1145
|
-
index: block.index,
|
|
1146
|
-
render,
|
|
1147
1437
|
track: trackVariants
|
|
1438
|
+
}),
|
|
1439
|
+
createElement("script", {
|
|
1440
|
+
nonce,
|
|
1441
|
+
dangerouslySetInnerHTML: { __html: APPLY_SCRIPT }
|
|
1148
1442
|
})
|
|
1149
1443
|
);
|
|
1150
1444
|
})
|
|
@@ -1156,33 +1450,59 @@ function createPreprClient2(options) {
|
|
|
1156
1450
|
const { cacheTags = ["prepr"], revalidate, ...coreOptions } = options;
|
|
1157
1451
|
const client = createPreprClient({
|
|
1158
1452
|
// The schema introspection goes through the Data Cache: shared across
|
|
1159
|
-
// server instances, refreshed hourly and by
|
|
1453
|
+
// server instances, refreshed hourly and by a `schema.changed` webhook.
|
|
1160
1454
|
schemaInit: {
|
|
1161
|
-
next: { tags: cacheTags, revalidate: 3600 }
|
|
1455
|
+
next: { tags: [...cacheTags, PREPR_SCHEMA_TAG], revalidate: 3600 }
|
|
1162
1456
|
},
|
|
1163
1457
|
...coreOptions
|
|
1164
1458
|
});
|
|
1165
|
-
const
|
|
1166
|
-
return {
|
|
1459
|
+
const nextClient = {
|
|
1167
1460
|
async query(document2, variables, queryOptions = {}) {
|
|
1168
|
-
const { personalize = false, ...rest } = queryOptions;
|
|
1169
|
-
const init = rest.init;
|
|
1170
|
-
|
|
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 = {
|
|
1171
1467
|
...rest,
|
|
1172
|
-
|
|
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" } : {
|
|
1173
1474
|
...init,
|
|
1174
1475
|
next: {
|
|
1175
|
-
tags: cacheTags,
|
|
1176
1476
|
...revalidate === void 0 ? {} : { revalidate },
|
|
1177
|
-
...
|
|
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 ?? []]
|
|
1178
1481
|
}
|
|
1179
1482
|
}
|
|
1180
1483
|
};
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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;
|
|
1186
1506
|
},
|
|
1187
1507
|
async decide(document2, variables, queryOptions = {}) {
|
|
1188
1508
|
return client.decide(document2, variables, {
|
|
@@ -1193,26 +1513,26 @@ function createPreprClient2(options) {
|
|
|
1193
1513
|
)
|
|
1194
1514
|
});
|
|
1195
1515
|
},
|
|
1196
|
-
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1197
|
-
resolveDecisions: (request, response, document2, variables, options2) => resolveDecisionsWith(
|
|
1198
|
-
client,
|
|
1199
|
-
seenGroups,
|
|
1200
|
-
request,
|
|
1201
|
-
response,
|
|
1202
|
-
document2,
|
|
1203
|
-
variables,
|
|
1204
|
-
options2
|
|
1205
|
-
)
|
|
1516
|
+
hasAbTests: (document2, variables) => client.hasAbTests(document2, variables)
|
|
1206
1517
|
};
|
|
1518
|
+
preprClientInternals.set(nextClient, { client });
|
|
1519
|
+
return nextClient;
|
|
1207
1520
|
}
|
|
1208
1521
|
export {
|
|
1209
|
-
|
|
1522
|
+
PREPR_ANY_ITEM_TAG,
|
|
1210
1523
|
PREPR_DEFAULT_VARIANT,
|
|
1211
1524
|
PreprError,
|
|
1212
1525
|
PreprStack,
|
|
1213
1526
|
createPreprClient2 as createPreprClient,
|
|
1527
|
+
createPreprProxy,
|
|
1528
|
+
decisionsFrom,
|
|
1214
1529
|
isPersonalizedResponse,
|
|
1530
|
+
loadPreprSchema,
|
|
1531
|
+
preprCacheTags,
|
|
1532
|
+
preprModelTag,
|
|
1215
1533
|
preprVariantProps,
|
|
1216
1534
|
resolveVariants,
|
|
1217
|
-
splitStack
|
|
1535
|
+
splitStack,
|
|
1536
|
+
toDecisionQuery,
|
|
1537
|
+
toVariantsQuery
|
|
1218
1538
|
};
|