@preprio/toolkit 0.5.0-rc.5 → 0.5.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +133 -26
  2. package/dist/astro/index.cjs +126 -30
  3. package/dist/astro/index.js +130 -30
  4. package/dist/{chunk-3X55JL52.js → chunk-HVS5QLWG.js} +1 -1
  5. package/dist/{chunk-TG6N3C22.js → chunk-IPMB32AZ.js} +1 -1
  6. package/dist/{chunk-CMUFTCG3.js → chunk-JUXP5KRX.js} +1 -1
  7. package/dist/{chunk-74KY5EAM.js → chunk-KD3GK6TO.js} +2 -0
  8. package/dist/{chunk-SBLZANL5.js → chunk-SUAFASBB.js} +11 -3
  9. package/dist/codegen/index.cjs +82 -0
  10. package/dist/codegen/index.d.cts +70 -0
  11. package/dist/codegen/index.d.ts +70 -0
  12. package/dist/codegen/index.js +64 -0
  13. package/dist/index.cjs +133 -28
  14. package/dist/index.d.cts +14 -2
  15. package/dist/index.d.ts +14 -2
  16. package/dist/index.js +108 -6
  17. package/dist/{live-preview-H4643QYF.js → live-preview-DLXGXRE5.js} +2 -2
  18. package/dist/nextjs/components.cjs +11 -3
  19. package/dist/nextjs/components.js +2 -2
  20. package/dist/nextjs/index.cjs +124 -28
  21. package/dist/nextjs/index.js +128 -28
  22. package/dist/nextjs/live-action.js +3 -3
  23. package/dist/nextjs/live.js +1 -1
  24. package/dist/nuxt/chunk-BWPGO25E.js +340 -0
  25. package/dist/nuxt/index.cjs +1 -1
  26. package/dist/nuxt/index.js +1 -1
  27. package/dist/nuxt/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
  28. package/dist/nuxt/live.cjs +1007 -911
  29. package/dist/nuxt/live.js +109 -5
  30. package/dist/react/components.cjs +11 -3
  31. package/dist/react/components.js +11 -3
  32. package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
  33. package/dist/sveltekit/index.cjs +1 -1
  34. package/dist/sveltekit/index.js +1 -1
  35. package/dist/sveltekit/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
  36. package/dist/sveltekit/live.cjs +1008 -909
  37. package/dist/sveltekit/live.js +118 -11
  38. package/package.json +11 -1
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.5";
2
+ var VERSION = "0.5.0-rc.7";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -402,14 +402,14 @@ function indexSnapshot(data) {
402
402
  const index = /* @__PURE__ */ new Map();
403
403
  const visited = /* @__PURE__ */ new WeakSet();
404
404
  const path = [];
405
- function walk(node) {
405
+ function walk2(node) {
406
406
  if (node === null || typeof node !== "object") return;
407
407
  if (visited.has(node)) return;
408
408
  visited.add(node);
409
409
  if (Array.isArray(node)) {
410
410
  for (let i = 0; i < node.length; i++) {
411
411
  path.push(i);
412
- walk(node[i]);
412
+ walk2(node[i]);
413
413
  path.pop();
414
414
  }
415
415
  return;
@@ -427,11 +427,11 @@ function indexSnapshot(data) {
427
427
  for (const key in record) {
428
428
  if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
429
429
  path.push(key);
430
- walk(record[key]);
430
+ walk2(record[key]);
431
431
  path.pop();
432
432
  }
433
433
  }
434
- walk(data);
434
+ walk2(data);
435
435
  return index;
436
436
  }
437
437
 
@@ -888,12 +888,12 @@ function resolveFields(selections, typename, doc, options) {
888
888
  }
889
889
  };
890
890
  const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
891
- const walk = (list, matched) => {
891
+ const walk2 = (list, matched) => {
892
892
  for (const s of list) {
893
893
  if (s.kind === "field") {
894
894
  add(matched ? exact : loose, s);
895
895
  } else if (s.kind === "inline") {
896
- walk(s.selections, matched && applies(s.on, s.selections));
896
+ walk2(s.selections, matched && applies(s.on, s.selections));
897
897
  } else {
898
898
  const fragment = doc.fragments.get(s.name);
899
899
  if (!fragment) continue;
@@ -901,11 +901,11 @@ function resolveFields(selections, typename, doc, options) {
901
901
  if (walkedMatched.has(fragment)) continue;
902
902
  if (!asMatched && walkedLoose.has(fragment)) continue;
903
903
  (asMatched ? walkedMatched : walkedLoose).add(fragment);
904
- walk(fragment.selections, asMatched);
904
+ walk2(fragment.selections, asMatched);
905
905
  }
906
906
  }
907
907
  };
908
- walk(selections, true);
908
+ walk2(selections, true);
909
909
  if (!options.strict) {
910
910
  for (const [key, field] of loose) {
911
911
  if (!exact.has(key)) exact.set(key, field);
@@ -922,21 +922,21 @@ function typeConditions(selections, doc) {
922
922
  if (hit) return hit;
923
923
  const names = /* @__PURE__ */ new Set();
924
924
  const seen = /* @__PURE__ */ new Set();
925
- const walk = (list) => {
925
+ const walk2 = (list) => {
926
926
  for (const s of list) {
927
927
  if (s.kind === "inline") {
928
928
  if (s.on) names.add(s.on);
929
- walk(s.selections);
929
+ walk2(s.selections);
930
930
  } else if (s.kind === "spread") {
931
931
  const fragment = doc.fragments.get(s.name);
932
932
  if (!fragment || seen.has(fragment)) continue;
933
933
  seen.add(fragment);
934
934
  names.add(fragment.on);
935
- walk(fragment.selections);
935
+ walk2(fragment.selections);
936
936
  }
937
937
  }
938
938
  };
939
- walk(selections);
939
+ walk2(selections);
940
940
  const conditions = [...names];
941
941
  conditionsCache.set(selections, conditions);
942
942
  return conditions;
@@ -1231,7 +1231,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
1231
1231
  let current = root;
1232
1232
  const out = [];
1233
1233
  const plain = /* @__PURE__ */ new Set();
1234
- const walk = (node, sel, i, path) => {
1234
+ const walk2 = (node, sel, i, path) => {
1235
1235
  if (i === segments.length) {
1236
1236
  out.push(path);
1237
1237
  return;
@@ -1241,10 +1241,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
1241
1241
  if (INDEX_SEGMENT.test(segment)) {
1242
1242
  const position = Number(segment);
1243
1243
  if (position < node.length) {
1244
- walk(node[position], sel, i + 1, [...path, position]);
1244
+ walk2(node[position], sel, i + 1, [...path, position]);
1245
1245
  }
1246
1246
  } else if (segment === "items") {
1247
- walk(node, sel, i + 1, path);
1247
+ walk2(node, sel, i + 1, path);
1248
1248
  }
1249
1249
  return;
1250
1250
  }
@@ -1277,11 +1277,11 @@ function resolveTargets(root, doc, ownerPath, segments) {
1277
1277
  current = writeAtPath(current, fieldPath, value);
1278
1278
  }
1279
1279
  }
1280
- walk(value, field.selections, i + 1, fieldPath);
1280
+ walk2(value, field.selections, i + 1, fieldPath);
1281
1281
  }
1282
- if (!matched && segment === "items") walk(node, sel, i + 1, path);
1282
+ if (!matched && segment === "items") walk2(node, sel, i + 1, path);
1283
1283
  };
1284
- walk(owner, selections, 0, ownerPath);
1284
+ walk2(owner, selections, 0, ownerPath);
1285
1285
  return { root: current, targets: out, plain };
1286
1286
  }
1287
1287
  var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
@@ -1404,11 +1404,11 @@ function listKeysIn(result) {
1404
1404
  if (!byType) {
1405
1405
  byType = /* @__PURE__ */ new Map();
1406
1406
  const seen = /* @__PURE__ */ new WeakSet();
1407
- const walk = (node) => {
1407
+ const walk2 = (node) => {
1408
1408
  if (node === null || typeof node !== "object" || seen.has(node)) return;
1409
1409
  seen.add(node);
1410
1410
  if (Array.isArray(node)) {
1411
- node.forEach(walk);
1411
+ node.forEach(walk2);
1412
1412
  return;
1413
1413
  }
1414
1414
  const record = node;
@@ -1419,10 +1419,10 @@ function listKeysIn(result) {
1419
1419
  if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
1420
1420
  keys.add(key);
1421
1421
  }
1422
- walk(value);
1422
+ walk2(value);
1423
1423
  }
1424
1424
  };
1425
- walk(result);
1425
+ walk2(result);
1426
1426
  listKeysCache.set(result, byType);
1427
1427
  }
1428
1428
  return byType.get(typename) ?? NO_KEYS;
@@ -1464,7 +1464,7 @@ function findEditLink(root) {
1464
1464
  if (linkCache.has(root)) return linkCache.get(root) ?? null;
1465
1465
  let found = null;
1466
1466
  const seen = /* @__PURE__ */ new WeakSet();
1467
- const walk = (node) => {
1467
+ const walk2 = (node) => {
1468
1468
  if (found) return;
1469
1469
  if (typeof node === "string") {
1470
1470
  if (!vercelStegaSplit2(node).encoded) return;
@@ -1479,9 +1479,9 @@ function findEditLink(root) {
1479
1479
  }
1480
1480
  if (node === null || typeof node !== "object" || seen.has(node)) return;
1481
1481
  seen.add(node);
1482
- for (const value of Object.values(node)) walk(value);
1482
+ for (const value of Object.values(node)) walk2(value);
1483
1483
  };
1484
- walk(root);
1484
+ walk2(root);
1485
1485
  linkCache.set(root, found);
1486
1486
  return found;
1487
1487
  }
@@ -1507,6 +1507,105 @@ async function readLiveLogFromRequest(request) {
1507
1507
  }
1508
1508
  }
1509
1509
 
1510
+ // src/core/live-preview/edit-links.ts
1511
+ import {
1512
+ vercelStegaCombine as vercelStegaCombine2,
1513
+ vercelStegaDecode as vercelStegaDecode2,
1514
+ vercelStegaSplit as vercelStegaSplit3
1515
+ } from "@vercel/stega";
1516
+ var ENTRY_PARAM = "entry";
1517
+ var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
1518
+ function entryIdOf(element) {
1519
+ if (!isRecord(element)) return void 0;
1520
+ if (typeof element._id === "string") return element._id;
1521
+ if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
1522
+ return void 0;
1523
+ }
1524
+ const first = element.items[0];
1525
+ return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
1526
+ }
1527
+ function isRecord(value) {
1528
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1529
+ }
1530
+ function annotate(value, ctx) {
1531
+ if (!ctx.entryId) return value;
1532
+ const { cleaned, encoded } = vercelStegaSplit3(value);
1533
+ if (!encoded) return value;
1534
+ let payload;
1535
+ try {
1536
+ payload = vercelStegaDecode2(encoded);
1537
+ } catch {
1538
+ return value;
1539
+ }
1540
+ if (typeof payload?.href !== "string") return value;
1541
+ let url;
1542
+ try {
1543
+ url = new URL(payload.href);
1544
+ } catch {
1545
+ return value;
1546
+ }
1547
+ if (url.searchParams.has(ENTRY_PARAM)) return value;
1548
+ url.searchParams.set(ENTRY_PARAM, ctx.entryId);
1549
+ const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
1550
+ if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
1551
+ url.searchParams.set("field", ctx.entryField);
1552
+ }
1553
+ return vercelStegaCombine2(
1554
+ cleaned,
1555
+ { ...payload, href: url.toString() },
1556
+ false
1557
+ );
1558
+ }
1559
+ function walk(value, selections, ctx) {
1560
+ if (typeof value === "string") return annotate(value, ctx);
1561
+ if (Array.isArray(value)) {
1562
+ let changed2 = false;
1563
+ const next2 = value.map((element, index) => {
1564
+ const path = [...ctx.path, String(index)];
1565
+ const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
1566
+ const entry = entryId ? {
1567
+ entryId,
1568
+ entryField: ctx.ownerId ? path.join(".") : void 0,
1569
+ entryOwnerId: ctx.ownerId,
1570
+ inAssets: isRecord(element) && element._id === void 0
1571
+ } : {};
1572
+ const out = walk(element, selections, { ...ctx, ...entry, path });
1573
+ if (out !== element) changed2 = true;
1574
+ return out;
1575
+ });
1576
+ return changed2 ? next2 : value;
1577
+ }
1578
+ if (!isRecord(value)) return value;
1579
+ const id = typeof value._id === "string" ? value._id : void 0;
1580
+ const typename = typeof value.__typename === "string" ? value.__typename : void 0;
1581
+ const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
1582
+ let changed = false;
1583
+ const next = {};
1584
+ for (const [key, child] of Object.entries(value)) {
1585
+ const field = fields?.get(key);
1586
+ const name = field?.name ?? key;
1587
+ const out = walk(child, field?.selections ?? null, {
1588
+ ...ctx,
1589
+ ownerId: id ?? ctx.ownerId,
1590
+ path: id ? [name] : [...ctx.path, name]
1591
+ });
1592
+ if (out !== child) changed = true;
1593
+ next[key] = out;
1594
+ }
1595
+ return changed ? next : value;
1596
+ }
1597
+ function annotateEditLinks(result, query, operationName) {
1598
+ let doc = null;
1599
+ if (query !== void 0) {
1600
+ try {
1601
+ doc = readQueryDocument(query, operationName);
1602
+ } catch {
1603
+ doc = null;
1604
+ }
1605
+ }
1606
+ return walk(result, doc?.operation ?? null, { doc, path: [] });
1607
+ }
1608
+
1510
1609
  // src/astro/index.ts
1511
1610
  var LIVE_LOG = /* @__PURE__ */ Symbol.for("@preprio/toolkit/astro-live-log");
1512
1611
  async function onPreprRequest(context, next, options) {
@@ -1547,10 +1646,11 @@ async function getToolbarProps(headers, token, features) {
1547
1646
  return getToolbarPropsFromHeaders(headers, token, features);
1548
1647
  }
1549
1648
  function normalizeQuery2(query, result, options = {}) {
1550
- if (!options.enabled || !options.locals) return result;
1551
- const log = options.locals[LIVE_LOG];
1552
- if (!log?.length) return result;
1553
- return normalizeQuery(query, result, {
1649
+ if (!options.enabled) return result;
1650
+ const annotated = annotateEditLinks(result, query, options.operationName);
1651
+ const log = options.locals?.[LIVE_LOG];
1652
+ if (!log?.length) return annotated;
1653
+ return normalizeQuery(query, annotated, {
1554
1654
  log,
1555
1655
  operationName: options.operationName
1556
1656
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VERSION,
3
3
  resolveFeatures
4
- } from "./chunk-CMUFTCG3.js";
4
+ } from "./chunk-JUXP5KRX.js";
5
5
 
6
6
  // src/core/server.ts
7
7
  var PreprError = class extends Error {
@@ -8,7 +8,7 @@ import {
8
8
  parseListEntries,
9
9
  patchSnapshot,
10
10
  syncListSnapshot
11
- } from "./chunk-74KY5EAM.js";
11
+ } from "./chunk-KD3GK6TO.js";
12
12
  import {
13
13
  createScopedLogger,
14
14
  getTrustedParentOrigin,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.5";
2
+ var VERSION = "0.5.0-rc.7";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -1210,6 +1210,8 @@ export {
1210
1210
  indexSnapshot,
1211
1211
  patchSnapshot,
1212
1212
  syncListSnapshot,
1213
+ readQueryDocument,
1214
+ collectFields,
1213
1215
  isLiveFieldChange,
1214
1216
  parseListEntries,
1215
1217
  parseEditTarget,
@@ -2,7 +2,7 @@ import {
2
2
  ALL_FEATURES_ENABLED,
3
3
  VERSION,
4
4
  resolveFeatures
5
- } from "./chunk-CMUFTCG3.js";
5
+ } from "./chunk-JUXP5KRX.js";
6
6
  import {
7
7
  createIframeBridge,
8
8
  isLocale,
@@ -480,6 +480,7 @@ function findFieldElement(lookup) {
480
480
 
481
481
  // src/core/stega/dom.ts
482
482
  var IGNORED_ANCESTORS = "script, style, noscript";
483
+ var AUTO_ENTRY_ATTR = "data-prepr-entry-auto";
483
484
  function isIgnoredTextNode(node) {
484
485
  if (node.parentElement?.closest(IGNORED_ANCESTORS)) return true;
485
486
  return !node.textContent?.trim();
@@ -513,7 +514,9 @@ function parseEditHref(href) {
513
514
  return {
514
515
  id: url.pathname.split("/").pop() || void 0,
515
516
  field: url.searchParams.get("field") || void 0,
516
- locale: url.searchParams.get("locale") || void 0
517
+ locale: url.searchParams.get("locale") || void 0,
518
+ // Added by the toolkit's `annotateEditLinks`, not by Prepr.
519
+ entry: url.searchParams.get("entry") || void 0
517
520
  };
518
521
  } catch {
519
522
  return {};
@@ -530,11 +533,16 @@ function tagEncodedElement(element, decoded) {
530
533
  element.setAttribute("data-prepr-encoded", "");
531
534
  element.setAttribute("data-prepr-href", decoded.href);
532
535
  element.setAttribute("data-prepr-origin", decoded.origin);
533
- const { id, field, locale } = parseEditHref(decoded.href);
536
+ const { id, field, locale, entry } = parseEditHref(decoded.href);
534
537
  setOrRemove(element, "data-prepr-id", id);
535
538
  const fieldHint = element.getAttribute("data-prepr-edit-target")?.trim() || void 0;
536
539
  setOrRemove(element, "data-prepr-field", field ?? fieldHint);
537
540
  setOrRemove(element, "data-prepr-locale", locale);
541
+ const siteEntry = element.hasAttribute("data-prepr-entry-id") && !element.hasAttribute(AUTO_ENTRY_ATTR);
542
+ if (!siteEntry) {
543
+ setOrRemove(element, "data-prepr-entry-id", entry);
544
+ setOrRemove(element, AUTO_ENTRY_ATTR, entry && "true");
545
+ }
538
546
  }
539
547
  function readEditPayload(element) {
540
548
  return {
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/codegen/index.ts
21
+ var codegen_exports = {};
22
+ __export(codegen_exports, {
23
+ addPreprIds: () => addPreprIds,
24
+ createPreprIds: () => createPreprIds,
25
+ preprIds: () => preprIds
26
+ });
27
+ module.exports = __toCommonJS(codegen_exports);
28
+ var import_graphql = require("graphql");
29
+ function field(name) {
30
+ return { kind: import_graphql.Kind.FIELD, name: { kind: import_graphql.Kind.NAME, value: name } };
31
+ }
32
+ function hasResponseKey(set, key) {
33
+ return set.selections.some(
34
+ (s) => s.kind === import_graphql.Kind.FIELD && (s.alias ?? s.name).value === key
35
+ );
36
+ }
37
+ function addPreprIds(document, schema, options = {}) {
38
+ const typename = options.typename ?? false;
39
+ const roots = new Set(
40
+ [
41
+ schema.getQueryType(),
42
+ schema.getMutationType(),
43
+ schema.getSubscriptionType()
44
+ ].filter(Boolean)
45
+ );
46
+ const typeInfo = new import_graphql.TypeInfo(schema);
47
+ return (0, import_graphql.visit)(
48
+ document,
49
+ (0, import_graphql.visitWithTypeInfo)(typeInfo, {
50
+ SelectionSet(node) {
51
+ const type = typeInfo.getParentType();
52
+ if (!type || !(0, import_graphql.isCompositeType)(type) || roots.has(type)) return;
53
+ const added = [];
54
+ if (((0, import_graphql.isObjectType)(type) || (0, import_graphql.isInterfaceType)(type)) && "_id" in type.getFields() && !hasResponseKey(node, "_id")) {
55
+ added.push(field("_id"));
56
+ }
57
+ if (typename && !hasResponseKey(node, "__typename")) {
58
+ added.push(field("__typename"));
59
+ }
60
+ if (!added.length) return;
61
+ return { ...node, selections: [...added, ...node.selections] };
62
+ }
63
+ })
64
+ );
65
+ }
66
+ function createPreprIds(options = {}) {
67
+ return {
68
+ transform({ documents, schema }) {
69
+ const built = (0, import_graphql.buildASTSchema)(schema, { assumeValidSDL: true });
70
+ return documents.map(
71
+ (file) => file.document ? { ...file, document: addPreprIds(file.document, built, options) } : file
72
+ );
73
+ }
74
+ };
75
+ }
76
+ var preprIds = createPreprIds();
77
+ // Annotate the CommonJS export names for ESM import in node:
78
+ 0 && (module.exports = {
79
+ addPreprIds,
80
+ createPreprIds,
81
+ preprIds
82
+ });
@@ -0,0 +1,70 @@
1
+ import { DocumentNode, GraphQLSchema } from 'graphql';
2
+
3
+ /**
4
+ * The slice of a GraphQL Codegen `DocumentFile` this transform touches,
5
+ * written out so the package needs no dependency on `@graphql-codegen/*`.
6
+ */
7
+ interface CodegenDocumentFile {
8
+ document?: DocumentNode;
9
+ }
10
+ interface PreprIdsOptions {
11
+ /**
12
+ * Also select `__typename` on every object, interface and union below the
13
+ * root. Default `false`: a selected `__typename` changes the types Codegen
14
+ * generates (each union member is typed separately, and objects built by
15
+ * hand must carry one), which can break code that compiles today. Live
16
+ * preview only needs it on dynamic content entries, where you select it
17
+ * already to render them.
18
+ */
19
+ typename?: boolean;
20
+ }
21
+ /**
22
+ * Select `_id` on every level of a query that has one, so live preview and
23
+ * click-to-edit can find each item without you selecting it by hand.
24
+ *
25
+ * The schema decides where it goes: only on types that have an `_id`
26
+ * (models and components, not collections like `Pages` or unions; inside a
27
+ * union, on each member). Adding a field never breaks the types Codegen
28
+ * generates. With `typename: true`, `__typename` is added on every object,
29
+ * interface and union below the root too. Fields already selected are left
30
+ * as they are.
31
+ */
32
+ declare function addPreprIds(document: DocumentNode, schema: GraphQLSchema, options?: PreprIdsOptions): DocumentNode;
33
+ /** A GraphQL Codegen document transform. */
34
+ interface CodegenDocumentTransform {
35
+ transform<File extends CodegenDocumentFile>(options: {
36
+ documents: File[];
37
+ schema: DocumentNode;
38
+ }): File[];
39
+ }
40
+ /**
41
+ * GraphQL Codegen document transform that runs `addPreprIds` on every
42
+ * document, with options:
43
+ *
44
+ * ```ts
45
+ * documentTransforms: [createPreprIds({ typename: true })],
46
+ * ```
47
+ */
48
+ declare function createPreprIds(options?: PreprIdsOptions): CodegenDocumentTransform;
49
+ /**
50
+ * GraphQL Codegen document transform that runs `addPreprIds` on every
51
+ * document, so the generated documents and types include `_id` wherever the
52
+ * schema has one:
53
+ *
54
+ * ```ts
55
+ * // codegen.ts
56
+ * import { preprIds } from '@preprio/toolkit/codegen';
57
+ *
58
+ * generates: {
59
+ * 'src/gql/': {
60
+ * preset: 'client',
61
+ * documentTransforms: [preprIds],
62
+ * },
63
+ * },
64
+ * ```
65
+ *
66
+ * `createPreprIds({ typename: true })` adds `__typename` as well.
67
+ */
68
+ declare const preprIds: CodegenDocumentTransform;
69
+
70
+ export { type CodegenDocumentFile, type CodegenDocumentTransform, type PreprIdsOptions, addPreprIds, createPreprIds, preprIds };
@@ -0,0 +1,70 @@
1
+ import { DocumentNode, GraphQLSchema } from 'graphql';
2
+
3
+ /**
4
+ * The slice of a GraphQL Codegen `DocumentFile` this transform touches,
5
+ * written out so the package needs no dependency on `@graphql-codegen/*`.
6
+ */
7
+ interface CodegenDocumentFile {
8
+ document?: DocumentNode;
9
+ }
10
+ interface PreprIdsOptions {
11
+ /**
12
+ * Also select `__typename` on every object, interface and union below the
13
+ * root. Default `false`: a selected `__typename` changes the types Codegen
14
+ * generates (each union member is typed separately, and objects built by
15
+ * hand must carry one), which can break code that compiles today. Live
16
+ * preview only needs it on dynamic content entries, where you select it
17
+ * already to render them.
18
+ */
19
+ typename?: boolean;
20
+ }
21
+ /**
22
+ * Select `_id` on every level of a query that has one, so live preview and
23
+ * click-to-edit can find each item without you selecting it by hand.
24
+ *
25
+ * The schema decides where it goes: only on types that have an `_id`
26
+ * (models and components, not collections like `Pages` or unions; inside a
27
+ * union, on each member). Adding a field never breaks the types Codegen
28
+ * generates. With `typename: true`, `__typename` is added on every object,
29
+ * interface and union below the root too. Fields already selected are left
30
+ * as they are.
31
+ */
32
+ declare function addPreprIds(document: DocumentNode, schema: GraphQLSchema, options?: PreprIdsOptions): DocumentNode;
33
+ /** A GraphQL Codegen document transform. */
34
+ interface CodegenDocumentTransform {
35
+ transform<File extends CodegenDocumentFile>(options: {
36
+ documents: File[];
37
+ schema: DocumentNode;
38
+ }): File[];
39
+ }
40
+ /**
41
+ * GraphQL Codegen document transform that runs `addPreprIds` on every
42
+ * document, with options:
43
+ *
44
+ * ```ts
45
+ * documentTransforms: [createPreprIds({ typename: true })],
46
+ * ```
47
+ */
48
+ declare function createPreprIds(options?: PreprIdsOptions): CodegenDocumentTransform;
49
+ /**
50
+ * GraphQL Codegen document transform that runs `addPreprIds` on every
51
+ * document, so the generated documents and types include `_id` wherever the
52
+ * schema has one:
53
+ *
54
+ * ```ts
55
+ * // codegen.ts
56
+ * import { preprIds } from '@preprio/toolkit/codegen';
57
+ *
58
+ * generates: {
59
+ * 'src/gql/': {
60
+ * preset: 'client',
61
+ * documentTransforms: [preprIds],
62
+ * },
63
+ * },
64
+ * ```
65
+ *
66
+ * `createPreprIds({ typename: true })` adds `__typename` as well.
67
+ */
68
+ declare const preprIds: CodegenDocumentTransform;
69
+
70
+ export { type CodegenDocumentFile, type CodegenDocumentTransform, type PreprIdsOptions, addPreprIds, createPreprIds, preprIds };
@@ -0,0 +1,64 @@
1
+ // src/codegen/index.ts
2
+ import {
3
+ buildASTSchema,
4
+ isCompositeType,
5
+ isInterfaceType,
6
+ isObjectType,
7
+ Kind,
8
+ TypeInfo,
9
+ visit,
10
+ visitWithTypeInfo
11
+ } from "graphql";
12
+ function field(name) {
13
+ return { kind: Kind.FIELD, name: { kind: Kind.NAME, value: name } };
14
+ }
15
+ function hasResponseKey(set, key) {
16
+ return set.selections.some(
17
+ (s) => s.kind === Kind.FIELD && (s.alias ?? s.name).value === key
18
+ );
19
+ }
20
+ function addPreprIds(document, schema, options = {}) {
21
+ const typename = options.typename ?? false;
22
+ const roots = new Set(
23
+ [
24
+ schema.getQueryType(),
25
+ schema.getMutationType(),
26
+ schema.getSubscriptionType()
27
+ ].filter(Boolean)
28
+ );
29
+ const typeInfo = new TypeInfo(schema);
30
+ return visit(
31
+ document,
32
+ visitWithTypeInfo(typeInfo, {
33
+ SelectionSet(node) {
34
+ const type = typeInfo.getParentType();
35
+ if (!type || !isCompositeType(type) || roots.has(type)) return;
36
+ const added = [];
37
+ if ((isObjectType(type) || isInterfaceType(type)) && "_id" in type.getFields() && !hasResponseKey(node, "_id")) {
38
+ added.push(field("_id"));
39
+ }
40
+ if (typename && !hasResponseKey(node, "__typename")) {
41
+ added.push(field("__typename"));
42
+ }
43
+ if (!added.length) return;
44
+ return { ...node, selections: [...added, ...node.selections] };
45
+ }
46
+ })
47
+ );
48
+ }
49
+ function createPreprIds(options = {}) {
50
+ return {
51
+ transform({ documents, schema }) {
52
+ const built = buildASTSchema(schema, { assumeValidSDL: true });
53
+ return documents.map(
54
+ (file) => file.document ? { ...file, document: addPreprIds(file.document, built, options) } : file
55
+ );
56
+ }
57
+ };
58
+ }
59
+ var preprIds = createPreprIds();
60
+ export {
61
+ addPreprIds,
62
+ createPreprIds,
63
+ preprIds
64
+ };