@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.
Files changed (59) hide show
  1. package/README.md +268 -59
  2. package/dist/astro/{chunk-3UVUDDNY.js → chunk-CG2QBHNU.js} +1 -1
  3. package/dist/astro/graphql.cjs +154 -59
  4. package/dist/astro/graphql.d.cts +75 -6
  5. package/dist/astro/graphql.d.ts +75 -6
  6. package/dist/astro/graphql.js +150 -59
  7. package/dist/astro/index.cjs +1 -1
  8. package/dist/astro/index.js +1 -1
  9. package/dist/chunk-3NCEVJJ3.js +10 -0
  10. package/dist/{chunk-BJ2LBLWO.js → chunk-LIDUJWWJ.js} +6 -4
  11. package/dist/chunk-M3RBYJ5J.js +499 -0
  12. package/dist/{chunk-DGZA7KX7.js → chunk-S5RWVMDJ.js} +0 -24
  13. package/dist/{chunk-YR7P4IWE.js → chunk-SF6TN4JR.js} +1 -9
  14. package/dist/chunk-TN6SNAR2.js +26 -0
  15. package/dist/graphql/codegen.cjs +557 -0
  16. package/dist/graphql/codegen.d.cts +23 -0
  17. package/dist/graphql/codegen.d.ts +23 -0
  18. package/dist/graphql/codegen.js +99 -0
  19. package/dist/graphql.cjs +185 -59
  20. package/dist/graphql.d.cts +110 -6
  21. package/dist/graphql.d.ts +110 -6
  22. package/dist/graphql.js +91 -430
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.cts +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +10 -6
  27. package/dist/nextjs/chunk-B4GVZEOL.js +549 -0
  28. package/dist/nextjs/components.cjs +297 -81
  29. package/dist/nextjs/components.d.cts +26 -11
  30. package/dist/nextjs/components.d.ts +26 -11
  31. package/dist/nextjs/components.js +289 -73
  32. package/dist/nextjs/graphql.cjs +952 -315
  33. package/dist/nextjs/graphql.d.cts +225 -83
  34. package/dist/nextjs/graphql.d.ts +225 -83
  35. package/dist/nextjs/graphql.js +634 -314
  36. package/dist/nextjs/index.cjs +116 -54
  37. package/dist/nextjs/index.d.cts +61 -23
  38. package/dist/nextjs/index.d.ts +61 -23
  39. package/dist/nextjs/index.js +97 -299
  40. package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.cts} +1 -1
  41. package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.ts} +1 -1
  42. package/dist/nuxt/{chunk-P6ENNAEE.js → chunk-FMC3DP7N.js} +1 -1
  43. package/dist/nuxt/graphql.cjs +154 -59
  44. package/dist/nuxt/graphql.d.cts +75 -6
  45. package/dist/nuxt/graphql.d.ts +75 -6
  46. package/dist/nuxt/graphql.js +150 -59
  47. package/dist/nuxt/index.cjs +1 -1
  48. package/dist/nuxt/index.js +1 -1
  49. package/dist/react/components.cjs +1 -1
  50. package/dist/react/components.js +1 -1
  51. package/dist/sveltekit/{chunk-FSARVMG5.js → chunk-AJPKBAHO.js} +1 -1
  52. package/dist/sveltekit/graphql.cjs +154 -59
  53. package/dist/sveltekit/graphql.d.cts +75 -6
  54. package/dist/sveltekit/graphql.d.ts +75 -6
  55. package/dist/sveltekit/graphql.js +150 -59
  56. package/dist/sveltekit/index.cjs +1 -1
  57. package/dist/sveltekit/index.js +1 -1
  58. package/package.json +6 -1
  59. package/dist/nextjs/chunk-U4HXENF7.js +0 -231
@@ -23,10 +23,14 @@ __export(graphql_exports, {
23
23
  PREPR_DEFAULT_VARIANT: () => PREPR_DEFAULT_VARIANT,
24
24
  PreprError: () => PreprError,
25
25
  createPreprClient: () => createPreprClient2,
26
+ decisionsFrom: () => decisionsFrom,
26
27
  isPersonalizedResponse: () => isPersonalizedResponse,
28
+ loadPreprSchema: () => loadPreprSchema,
27
29
  preprVariantProps: () => preprVariantProps,
28
30
  resolveVariants: () => resolveVariants,
29
- splitStack: () => splitStack
31
+ splitStack: () => splitStack,
32
+ toDecisionQuery: () => toDecisionQuery,
33
+ toVariantsQuery: () => toVariantsQuery
30
34
  });
31
35
  module.exports = __toCommonJS(graphql_exports);
32
36
 
@@ -174,7 +178,10 @@ function decisionSelectionSet(set, typeName, path, ctx) {
174
178
  path,
175
179
  ctx
176
180
  );
177
- if (pruned) {
181
+ if (!pruned) continue;
182
+ if (fragment.typeCondition.name.value === typeName) {
183
+ selections.push(...pruned.selections);
184
+ } else {
178
185
  selections.push(
179
186
  inlineFragment(fragment.typeCondition.name.value, [
180
187
  ...pruned.selections
@@ -184,13 +191,19 @@ function decisionSelectionSet(set, typeName, path, ctx) {
184
191
  continue;
185
192
  }
186
193
  if (selection.kind === "InlineFragment") {
194
+ const condition = selection.typeCondition?.name.value ?? typeName;
187
195
  const pruned = decisionSelectionSet(
188
196
  selection.selectionSet,
189
- selection.typeCondition?.name.value ?? typeName,
197
+ condition,
190
198
  path,
191
199
  ctx
192
200
  );
193
- if (pruned) selections.push({ ...selection, selectionSet: pruned });
201
+ if (!pruned) continue;
202
+ if (condition === typeName && !selection.directives?.length) {
203
+ selections.push(...pruned.selections);
204
+ } else {
205
+ selections.push({ ...selection, selectionSet: pruned });
206
+ }
194
207
  continue;
195
208
  }
196
209
  if (!selection.selectionSet) continue;
@@ -264,20 +277,58 @@ function toDecisionDocument(document2, schema, groupPaths, options) {
264
277
  const used = /* @__PURE__ */ new Set();
265
278
  collectVariables(pruned, used);
266
279
  collectVariables(operation.directives, used);
280
+ const shared = sharedContextFragment(schema);
267
281
  return {
268
282
  kind: "Document",
269
283
  definitions: [
284
+ ...shared ? [shared.definition] : [],
270
285
  {
271
286
  ...operation,
272
287
  name: operation.name && name(`${operation.name.value}Decision`),
273
288
  variableDefinitions: (operation.variableDefinitions ?? []).filter(
274
289
  (definition) => used.has(definition.variable.name.value)
275
290
  ),
276
- selectionSet: pruned
291
+ selectionSet: shared ? shared.apply(pruned) : pruned
277
292
  }
278
293
  ]
279
294
  };
280
295
  }
296
+ function sharedContextFragment(schema) {
297
+ const contextTypes = new Set(
298
+ Object.values(schema.types).flatMap(
299
+ (type) => type.hasContext ? [type.fields._context.type] : []
300
+ )
301
+ );
302
+ if (contextTypes.size !== 1) return null;
303
+ const [contextType] = contextTypes;
304
+ const fragmentName = "c";
305
+ const definition = {
306
+ kind: "FragmentDefinition",
307
+ name: name(fragmentName),
308
+ typeCondition: { kind: "NamedType", name: name(contextType) },
309
+ selectionSet: selectionSet(
310
+ ["kind", "group_id", "variant_id"].map(
311
+ (field) => ({ kind: "Field", name: name(field) })
312
+ )
313
+ )
314
+ };
315
+ const spread = {
316
+ kind: "FragmentSpread",
317
+ name: name(fragmentName)
318
+ };
319
+ const apply = (set) => selectionSet(
320
+ set.selections.map((selection) => {
321
+ if (selection.kind === "Field" && selection.alias?.value === PREPR_CONTEXT_ALIAS) {
322
+ return { ...selection, selectionSet: selectionSet([spread]) };
323
+ }
324
+ return "selectionSet" in selection && selection.selectionSet ? {
325
+ ...selection,
326
+ selectionSet: apply(selection.selectionSet)
327
+ } : selection;
328
+ })
329
+ );
330
+ return { definition, apply };
331
+ }
281
332
 
282
333
  // src/core/variants/probe-document.ts
283
334
  function probeSelectionSet(set, fragments) {
@@ -564,6 +615,63 @@ function toVariantsDocument(document2, schema) {
564
615
  };
565
616
  }
566
617
 
618
+ // src/core/graphql/document.ts
619
+ var import_printer = require("graphql/language/printer.js");
620
+ var import_stripIgnoredCharacters = require("graphql/utilities/stripIgnoredCharacters.js");
621
+ var printed = /* @__PURE__ */ new WeakMap();
622
+ function isDocumentNode(value) {
623
+ return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
624
+ }
625
+ function invalidDocument(originalError) {
626
+ return new PreprError(
627
+ "Expected a GraphQL query string, TypedDocumentString or DocumentNode",
628
+ "INVALID_DOCUMENT",
629
+ "createPreprClient",
630
+ originalError
631
+ );
632
+ }
633
+ var minified = /* @__PURE__ */ new Map();
634
+ var MINIFIED_CACHE_LIMIT = 500;
635
+ function minify(query) {
636
+ const cached = minified.get(query);
637
+ if (cached !== void 0) return cached;
638
+ let result = query;
639
+ try {
640
+ result = (0, import_stripIgnoredCharacters.stripIgnoredCharacters)(query);
641
+ } catch {
642
+ }
643
+ if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
644
+ return result;
645
+ }
646
+ function resolveQuery(document2) {
647
+ if (typeof document2 === "string") return minify(document2);
648
+ if (document2 instanceof String) return minify(document2.toString());
649
+ if (!isDocumentNode(document2)) throw invalidDocument();
650
+ let query = printed.get(document2);
651
+ if (query === void 0) {
652
+ try {
653
+ query = minify((0, import_printer.print)(document2));
654
+ } catch (error) {
655
+ throw invalidDocument(error instanceof Error ? error : void 0);
656
+ }
657
+ printed.set(document2, query);
658
+ }
659
+ return query;
660
+ }
661
+
662
+ // src/core/variants/queries.ts
663
+ function toVariantsQuery(document2, schema) {
664
+ return resolveQuery(toVariantsDocument(document2, schema));
665
+ }
666
+ function toDecisionQuery(document2, schema, options = {}) {
667
+ const paths = options.from === void 0 ? void 0 : personalizationPaths(options.from);
668
+ if (paths && paths.size === 0) return null;
669
+ const decision = toDecisionDocument(document2, schema, paths, {
670
+ topLevelOnly: !paths && options.topLevelOnly
671
+ });
672
+ return decision ? resolveQuery(decision) : null;
673
+ }
674
+
567
675
  // src/core/graphql/transport.ts
568
676
  var import_stega2 = require("@vercel/stega");
569
677
 
@@ -623,50 +731,6 @@ function stegaClean(text) {
623
731
  return (0, import_stega.vercelStegaClean)(text);
624
732
  }
625
733
 
626
- // src/core/graphql/document.ts
627
- var import_printer = require("graphql/language/printer.js");
628
- var import_stripIgnoredCharacters = require("graphql/utilities/stripIgnoredCharacters.js");
629
- var printed = /* @__PURE__ */ new WeakMap();
630
- function isDocumentNode(value) {
631
- return typeof value === "object" && value !== null && value.kind === "Document" && Array.isArray(value.definitions);
632
- }
633
- function invalidDocument(originalError) {
634
- return new PreprError(
635
- "Expected a GraphQL query string, TypedDocumentString or DocumentNode",
636
- "INVALID_DOCUMENT",
637
- "createPreprClient",
638
- originalError
639
- );
640
- }
641
- var minified = /* @__PURE__ */ new Map();
642
- var MINIFIED_CACHE_LIMIT = 500;
643
- function minify(query) {
644
- const cached = minified.get(query);
645
- if (cached !== void 0) return cached;
646
- let result = query;
647
- try {
648
- result = (0, import_stripIgnoredCharacters.stripIgnoredCharacters)(query);
649
- } catch {
650
- }
651
- if (minified.size < MINIFIED_CACHE_LIMIT) minified.set(query, result);
652
- return result;
653
- }
654
- function resolveQuery(document2) {
655
- if (typeof document2 === "string") return minify(document2);
656
- if (document2 instanceof String) return minify(document2.toString());
657
- if (!isDocumentNode(document2)) throw invalidDocument();
658
- let query = printed.get(document2);
659
- if (query === void 0) {
660
- try {
661
- query = minify((0, import_printer.print)(document2));
662
- } catch (error) {
663
- throw invalidDocument(error instanceof Error ? error : void 0);
664
- }
665
- printed.set(document2, query);
666
- }
667
- return query;
668
- }
669
-
670
734
  // src/core/graphql/transport.ts
671
735
  var OPERATION_NAME = /\b(?:query|mutation|subscription)\s+([_A-Za-z][_0-9A-Za-z]*)/;
672
736
  var STEGA_RAW = new RegExp(
@@ -845,6 +909,7 @@ function createVaryProbe(run, options) {
845
909
  }
846
910
 
847
911
  // src/core/graphql/client.ts
912
+ var clientSchemas = /* @__PURE__ */ new WeakMap();
848
913
  function isParseableUrl(url) {
849
914
  try {
850
915
  new URL(url);
@@ -853,16 +918,18 @@ function isParseableUrl(url) {
853
918
  return false;
854
919
  }
855
920
  }
856
- function createPreprClient(options) {
857
- const { url } = options;
921
+ function assertPreprUrl(url, source) {
858
922
  validatePreprToken(url);
859
923
  if (!isParseableUrl(url)) {
860
924
  throw new PreprError(
861
925
  "Prepr GraphQL URL is not a valid URL",
862
926
  "INVALID_TOKEN",
863
- "createPreprClient"
927
+ source
864
928
  );
865
929
  }
930
+ }
931
+ function createPreprClient(options) {
932
+ assertPreprUrl(options.url, "createPreprClient");
866
933
  const run = createTransport(options);
867
934
  const probeVary = createVaryProbe(run, options);
868
935
  let schema;
@@ -879,7 +946,7 @@ function createPreprClient(options) {
879
946
  ));
880
947
  const variantsDocuments = /* @__PURE__ */ new Map();
881
948
  const decisionDocuments = /* @__PURE__ */ new Map();
882
- return {
949
+ const client = {
883
950
  async query(document2, variables, queryOptions = {}) {
884
951
  const { variants, ...rest } = queryOptions;
885
952
  if (!variants) return run(document2, variables, rest);
@@ -891,10 +958,10 @@ function createPreprClient(options) {
891
958
  return run(derived, variables, rest);
892
959
  },
893
960
  async decide(document2, variables, queryOptions = {}) {
894
- const { from, topLevelOnly, ...rest } = queryOptions;
961
+ const { from, ...rest } = queryOptions;
895
962
  const paths = from === void 0 ? void 0 : personalizationPaths(from);
896
963
  if (paths && paths.size === 0) return /* @__PURE__ */ new Map();
897
- const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : topLevelOnly ? "top" : "*";
964
+ const signature = paths ? [...paths].map(([path, types]) => `${path}:${[...types].sort().join(",")}`).sort().join("|") : "*";
898
965
  let byPaths = decisionDocuments.get(document2);
899
966
  if (!byPaths) {
900
967
  byPaths = /* @__PURE__ */ new Map();
@@ -902,9 +969,7 @@ function createPreprClient(options) {
902
969
  }
903
970
  let derived = byPaths.get(signature);
904
971
  if (derived === void 0) {
905
- derived = toDecisionDocument(document2, await loadSchema(), paths, {
906
- topLevelOnly: !paths && topLevelOnly
907
- });
972
+ derived = toDecisionDocument(document2, await loadSchema(), paths);
908
973
  byPaths.set(signature, derived);
909
974
  }
910
975
  if (!derived) return /* @__PURE__ */ new Map();
@@ -918,6 +983,8 @@ function createPreprClient(options) {
918
983
  (vary) => vary === null || SEGMENTS_VARY.test(vary)
919
984
  )
920
985
  };
986
+ clientSchemas.set(client, loadSchema);
987
+ return client;
921
988
  }
922
989
 
923
990
  // src/core/graphql/event-client.ts
@@ -965,6 +1032,30 @@ function createEventClient(options, adapter) {
965
1032
  };
966
1033
  }
967
1034
 
1035
+ // src/core/graphql/schema-loader.ts
1036
+ var schemas = /* @__PURE__ */ new Map();
1037
+ async function loadPreprSchema(url, options = {}) {
1038
+ assertPreprUrl(url, "loadPreprSchema");
1039
+ let schema = schemas.get(url);
1040
+ if (!schema) {
1041
+ const run = createTransport({ url, fetch: options.fetch, stega: "none" });
1042
+ schema = run(
1043
+ PREPR_SCHEMA_QUERY
1044
+ ).then((result) => toSchemaInfo(result.data)).catch((error) => {
1045
+ schemas.delete(url);
1046
+ if (error instanceof PreprError) throw error;
1047
+ throw new PreprError(
1048
+ "Prepr schema response is not an introspection result",
1049
+ "INVALID_RESPONSE",
1050
+ "loadPreprSchema",
1051
+ error instanceof Error ? error : void 0
1052
+ );
1053
+ });
1054
+ schemas.set(url, schema);
1055
+ }
1056
+ return schema;
1057
+ }
1058
+
968
1059
  // src/astro/graphql.ts
969
1060
  function createPreprClient2(options) {
970
1061
  return createEventClient(options, {
@@ -976,8 +1067,12 @@ function createPreprClient2(options) {
976
1067
  PREPR_DEFAULT_VARIANT,
977
1068
  PreprError,
978
1069
  createPreprClient,
1070
+ decisionsFrom,
979
1071
  isPersonalizedResponse,
1072
+ loadPreprSchema,
980
1073
  preprVariantProps,
981
1074
  resolveVariants,
982
- splitStack
1075
+ splitStack,
1076
+ toDecisionQuery,
1077
+ toVariantsQuery
983
1078
  });
@@ -7,6 +7,12 @@ declare const PREPR_DEFAULT_VARIANT = "ALL_OTHER_USERS";
7
7
 
8
8
  /** The variant ids Prepr chose for one visitor, per personalization group. */
9
9
  type PreprDecisions = ReadonlyMap<string, readonly string[]>;
10
+ /**
11
+ * Variant ids per group from a decision response, de-duplicated and in
12
+ * Prepr's order. A visitor in several segments can get more than one variant
13
+ * of the same group, so each entry is a list.
14
+ */
15
+ declare function decisionsFrom(data: unknown): PreprDecisions;
10
16
  interface ResolveVariantsOptions {
11
17
  /**
12
18
  * The visitor's per-group variants (from a decision query). Without it,
@@ -23,6 +29,24 @@ interface ResolveVariantsOptions {
23
29
  */
24
30
  declare function resolveVariants<T>(data: T, options?: ResolveVariantsOptions): T;
25
31
 
32
+ interface FieldInfo {
33
+ /** Named type of the field, without list/non-null wrappers. */
34
+ type: string;
35
+ /** The field accepts Prepr's `personalize` argument (a stack). */
36
+ personalizable: boolean;
37
+ }
38
+ interface TypeInfo {
39
+ fields: Record<string, FieldInfo>;
40
+ /** Members of a union or implementations of an interface. */
41
+ possibleTypes: string[];
42
+ hasContext: boolean;
43
+ }
44
+ /** The slice of the schema the variant transforms need. */
45
+ interface PreprSchemaInfo {
46
+ queryType: string;
47
+ types: Record<string, TypeInfo>;
48
+ }
49
+
26
50
  /** A run of stack items that renders the same way: see {@link splitStack}. */
27
51
  interface PreprStackBlock<T> {
28
52
  key: string;
@@ -164,11 +188,6 @@ interface PreprDecideOptions extends Omit<PreprQueryOptions, 'variants'> {
164
188
  * none, e.g. on pages with only A/B tests or no personalization.
165
189
  */
166
190
  from?: unknown;
167
- /**
168
- * @internal Ask only about the first personalizable list on each path,
169
- * without `from`: what middleware needs for groups at the top of a stack.
170
- */
171
- topLevelOnly?: boolean;
172
191
  }
173
192
  interface PreprQueryResult<TResult> {
174
193
  data: TResult;
@@ -179,6 +198,39 @@ interface PreprQueryResult<TResult> {
179
198
  personalized: boolean;
180
199
  }
181
200
 
201
+ /**
202
+ * The schema facts the query rewrites need, from `loadPreprSchema`. Pass it
203
+ * along; its shape is not part of the public API.
204
+ */
205
+ type PreprSchema = PreprSchemaInfo;
206
+ /**
207
+ * Your query, asking for every personalization and A/B variant: what
208
+ * `query(..., { variants: true })` sends, as a string for your own GraphQL
209
+ * client. The result is the same for every visitor, so cache it like any
210
+ * other content query, and render it with `resolveVariants` or `PreprStack`.
211
+ *
212
+ * A query without personalizable fields comes back unchanged (minified).
213
+ */
214
+ declare function toVariantsQuery(document: PreprDocument<unknown, unknown> | string, schema: PreprSchema): string;
215
+ /**
216
+ * The per-visitor decision query for the same query: send it with the
217
+ * visitor's Prepr headers, never cached, and pass the response's `data` to
218
+ * `decisionsFrom`.
219
+ *
220
+ * Pass the variants result as `from` whenever you have it: the query then
221
+ * asks only about the lists that hold segment groups on this page, which is
222
+ * a few hundred bytes instead of the whole page and markedly faster for a
223
+ * visitor Prepr has not seen before. Without `from` it asks about every
224
+ * personalizable list. `topLevelOnly` stops at the first personalizable list
225
+ * on each path, for middleware that decides only the top of a stack.
226
+ *
227
+ * Null when there is nothing to decide: send no request then.
228
+ */
229
+ declare function toDecisionQuery(document: PreprDocument<unknown, unknown> | string, schema: PreprSchema, options?: {
230
+ from?: unknown;
231
+ topLevelOnly?: boolean;
232
+ }): string | null;
233
+
182
234
  /**
183
235
  * Query options for frameworks without an implicit request scope.
184
236
  * `personalize: true` requires the request `event`; the compiler enforces it.
@@ -211,6 +263,23 @@ interface PreprEventClient<TEvent> {
211
263
  }): Promise<PreprDecisions>;
212
264
  }
213
265
 
266
+ /**
267
+ * Loads what `toVariantsQuery` and `toDecisionQuery` need to know about your
268
+ * Prepr schema (which fields take `personalize`, which types can hold
269
+ * variants) with one introspection request, kept in memory per URL. For a
270
+ * site that uses its own GraphQL client; `createPreprClient` does this
271
+ * itself.
272
+ *
273
+ * Load it once at startup or lazily before the first rewrite. Schema changes
274
+ * in Prepr take effect after a restart or redeploy.
275
+ *
276
+ * @throws PreprError when the URL is invalid or the request fails; the URL,
277
+ * which holds your access token, is never part of the message.
278
+ */
279
+ declare function loadPreprSchema(url: string, options?: {
280
+ fetch?: typeof fetch;
281
+ }): Promise<PreprSchemaInfo>;
282
+
214
283
  /** Error thrown by the token helpers and the segments fetch. */
215
284
  declare class PreprError extends Error {
216
285
  readonly code: PreprErrorCode;
@@ -228,4 +297,4 @@ declare class PreprError extends Error {
228
297
  */
229
298
  declare function createPreprClient(options: PreprClientOptions): PreprEventClient<AstroLikeContext>;
230
299
 
231
- export { AstroLikeContext, PREPR_DEFAULT_VARIANT, type PreprClientOptions, type PreprDecideOptions, type PreprDecisions, type PreprDocument, PreprError, type PreprEventClient, type PreprEventQueryOptions, type PreprQueryResult, type PreprStackBlock, type PreprStegaMode, type PreprVariantProps, type ResolveVariantsOptions, createPreprClient, preprVariantProps, resolveVariants, splitStack };
300
+ export { AstroLikeContext, PREPR_DEFAULT_VARIANT, type PreprClientOptions, type PreprDecideOptions, type PreprDecisions, type PreprDocument, PreprError, type PreprEventClient, type PreprEventQueryOptions, type PreprQueryResult, type PreprSchema, type PreprStackBlock, type PreprStegaMode, type PreprVariantProps, type ResolveVariantsOptions, createPreprClient, decisionsFrom, loadPreprSchema, preprVariantProps, resolveVariants, splitStack, toDecisionQuery, toVariantsQuery };
@@ -7,6 +7,12 @@ declare const PREPR_DEFAULT_VARIANT = "ALL_OTHER_USERS";
7
7
 
8
8
  /** The variant ids Prepr chose for one visitor, per personalization group. */
9
9
  type PreprDecisions = ReadonlyMap<string, readonly string[]>;
10
+ /**
11
+ * Variant ids per group from a decision response, de-duplicated and in
12
+ * Prepr's order. A visitor in several segments can get more than one variant
13
+ * of the same group, so each entry is a list.
14
+ */
15
+ declare function decisionsFrom(data: unknown): PreprDecisions;
10
16
  interface ResolveVariantsOptions {
11
17
  /**
12
18
  * The visitor's per-group variants (from a decision query). Without it,
@@ -23,6 +29,24 @@ interface ResolveVariantsOptions {
23
29
  */
24
30
  declare function resolveVariants<T>(data: T, options?: ResolveVariantsOptions): T;
25
31
 
32
+ interface FieldInfo {
33
+ /** Named type of the field, without list/non-null wrappers. */
34
+ type: string;
35
+ /** The field accepts Prepr's `personalize` argument (a stack). */
36
+ personalizable: boolean;
37
+ }
38
+ interface TypeInfo {
39
+ fields: Record<string, FieldInfo>;
40
+ /** Members of a union or implementations of an interface. */
41
+ possibleTypes: string[];
42
+ hasContext: boolean;
43
+ }
44
+ /** The slice of the schema the variant transforms need. */
45
+ interface PreprSchemaInfo {
46
+ queryType: string;
47
+ types: Record<string, TypeInfo>;
48
+ }
49
+
26
50
  /** A run of stack items that renders the same way: see {@link splitStack}. */
27
51
  interface PreprStackBlock<T> {
28
52
  key: string;
@@ -164,11 +188,6 @@ interface PreprDecideOptions extends Omit<PreprQueryOptions, 'variants'> {
164
188
  * none, e.g. on pages with only A/B tests or no personalization.
165
189
  */
166
190
  from?: unknown;
167
- /**
168
- * @internal Ask only about the first personalizable list on each path,
169
- * without `from`: what middleware needs for groups at the top of a stack.
170
- */
171
- topLevelOnly?: boolean;
172
191
  }
173
192
  interface PreprQueryResult<TResult> {
174
193
  data: TResult;
@@ -179,6 +198,39 @@ interface PreprQueryResult<TResult> {
179
198
  personalized: boolean;
180
199
  }
181
200
 
201
+ /**
202
+ * The schema facts the query rewrites need, from `loadPreprSchema`. Pass it
203
+ * along; its shape is not part of the public API.
204
+ */
205
+ type PreprSchema = PreprSchemaInfo;
206
+ /**
207
+ * Your query, asking for every personalization and A/B variant: what
208
+ * `query(..., { variants: true })` sends, as a string for your own GraphQL
209
+ * client. The result is the same for every visitor, so cache it like any
210
+ * other content query, and render it with `resolveVariants` or `PreprStack`.
211
+ *
212
+ * A query without personalizable fields comes back unchanged (minified).
213
+ */
214
+ declare function toVariantsQuery(document: PreprDocument<unknown, unknown> | string, schema: PreprSchema): string;
215
+ /**
216
+ * The per-visitor decision query for the same query: send it with the
217
+ * visitor's Prepr headers, never cached, and pass the response's `data` to
218
+ * `decisionsFrom`.
219
+ *
220
+ * Pass the variants result as `from` whenever you have it: the query then
221
+ * asks only about the lists that hold segment groups on this page, which is
222
+ * a few hundred bytes instead of the whole page and markedly faster for a
223
+ * visitor Prepr has not seen before. Without `from` it asks about every
224
+ * personalizable list. `topLevelOnly` stops at the first personalizable list
225
+ * on each path, for middleware that decides only the top of a stack.
226
+ *
227
+ * Null when there is nothing to decide: send no request then.
228
+ */
229
+ declare function toDecisionQuery(document: PreprDocument<unknown, unknown> | string, schema: PreprSchema, options?: {
230
+ from?: unknown;
231
+ topLevelOnly?: boolean;
232
+ }): string | null;
233
+
182
234
  /**
183
235
  * Query options for frameworks without an implicit request scope.
184
236
  * `personalize: true` requires the request `event`; the compiler enforces it.
@@ -211,6 +263,23 @@ interface PreprEventClient<TEvent> {
211
263
  }): Promise<PreprDecisions>;
212
264
  }
213
265
 
266
+ /**
267
+ * Loads what `toVariantsQuery` and `toDecisionQuery` need to know about your
268
+ * Prepr schema (which fields take `personalize`, which types can hold
269
+ * variants) with one introspection request, kept in memory per URL. For a
270
+ * site that uses its own GraphQL client; `createPreprClient` does this
271
+ * itself.
272
+ *
273
+ * Load it once at startup or lazily before the first rewrite. Schema changes
274
+ * in Prepr take effect after a restart or redeploy.
275
+ *
276
+ * @throws PreprError when the URL is invalid or the request fails; the URL,
277
+ * which holds your access token, is never part of the message.
278
+ */
279
+ declare function loadPreprSchema(url: string, options?: {
280
+ fetch?: typeof fetch;
281
+ }): Promise<PreprSchemaInfo>;
282
+
214
283
  /** Error thrown by the token helpers and the segments fetch. */
215
284
  declare class PreprError extends Error {
216
285
  readonly code: PreprErrorCode;
@@ -228,4 +297,4 @@ declare class PreprError extends Error {
228
297
  */
229
298
  declare function createPreprClient(options: PreprClientOptions): PreprEventClient<AstroLikeContext>;
230
299
 
231
- export { AstroLikeContext, PREPR_DEFAULT_VARIANT, type PreprClientOptions, type PreprDecideOptions, type PreprDecisions, type PreprDocument, PreprError, type PreprEventClient, type PreprEventQueryOptions, type PreprQueryResult, type PreprStackBlock, type PreprStegaMode, type PreprVariantProps, type ResolveVariantsOptions, createPreprClient, preprVariantProps, resolveVariants, splitStack };
300
+ export { AstroLikeContext, PREPR_DEFAULT_VARIANT, type PreprClientOptions, type PreprDecideOptions, type PreprDecisions, type PreprDocument, PreprError, type PreprEventClient, type PreprEventQueryOptions, type PreprQueryResult, type PreprSchema, type PreprStackBlock, type PreprStegaMode, type PreprVariantProps, type ResolveVariantsOptions, createPreprClient, decisionsFrom, loadPreprSchema, preprVariantProps, resolveVariants, splitStack, toDecisionQuery, toVariantsQuery };