@tinacms/graphql 0.0.0-ecea7ac-20241011043815 → 0.0.0-ed6025e-20251201040055

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/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/build.ts
2
- import { print } from "graphql";
3
- import uniqBy2 from "lodash.uniqby";
2
+ import {
3
+ print
4
+ } from "graphql";
5
+ import { uniqBy as uniqBy2 } from "es-toolkit";
4
6
 
5
7
  // src/util.ts
6
8
  import * as yup from "yup";
@@ -56,7 +58,7 @@ var flattenDeep = (arr) => arr.flatMap(
56
58
  );
57
59
 
58
60
  // src/ast-builder/index.ts
59
- import uniqBy from "lodash.uniqby";
61
+ import { uniqBy } from "es-toolkit";
60
62
  var SysFieldDefinition = {
61
63
  kind: "Field",
62
64
  name: {
@@ -68,6 +70,15 @@ var SysFieldDefinition = {
68
70
  selectionSet: {
69
71
  kind: "SelectionSet",
70
72
  selections: [
73
+ // {
74
+ // kind: 'Field' as const,
75
+ // name: {
76
+ // kind: 'Name' as const,
77
+ // value: 'title',
78
+ // },
79
+ // arguments: [],
80
+ // directives: [],
81
+ // },
71
82
  {
72
83
  kind: "Field",
73
84
  name: {
@@ -86,6 +97,15 @@ var SysFieldDefinition = {
86
97
  arguments: [],
87
98
  directives: []
88
99
  },
100
+ {
101
+ kind: "Field",
102
+ name: {
103
+ kind: "Name",
104
+ value: "hasReferences"
105
+ },
106
+ arguments: [],
107
+ directives: []
108
+ },
89
109
  {
90
110
  kind: "Field",
91
111
  name: {
@@ -126,6 +146,10 @@ var SysFieldDefinition = {
126
146
  }
127
147
  };
128
148
  var astBuilder = {
149
+ /**
150
+ * `FormFieldBuilder` acts as a shortcut to building an entire `ObjectTypeDefinition`, we use this
151
+ * because all Tina field objects share a common set of fields ('name', 'label', 'component')
152
+ */
129
153
  FormFieldBuilder: ({
130
154
  name,
131
155
  additionalFields
@@ -349,6 +373,8 @@ var astBuilder = {
349
373
  kind: "Name",
350
374
  value: name
351
375
  },
376
+ // @ts-ignore FIXME; this is being handled properly but we're lying to
377
+ // ts and then fixing it in the `extractInlineTypes` function
352
378
  fields
353
379
  }),
354
380
  UnionTypeDefinition: ({
@@ -361,6 +387,8 @@ var astBuilder = {
361
387
  value: name
362
388
  },
363
389
  directives: [],
390
+ // @ts-ignore FIXME; this is being handled properly but we're lying to
391
+ // ts and then fixing it in the `extractInlineTypes` function
364
392
  types: types.map((name2) => ({
365
393
  kind: "NamedType",
366
394
  name: {
@@ -457,8 +485,11 @@ var astBuilder = {
457
485
  string: "String",
458
486
  boolean: "Boolean",
459
487
  number: "Float",
488
+ // FIXME - needs to be float or int
460
489
  datetime: "String",
490
+ // FIXME
461
491
  image: "String",
492
+ // FIXME
462
493
  text: "String"
463
494
  };
464
495
  return scalars[type];
@@ -948,6 +979,7 @@ var astBuilder = {
948
979
  ...extractInlineTypes(ast.globalTemplates),
949
980
  ...ast.definitions
950
981
  ],
982
+ // @ts-ignore - all nodes have a name property in practice
951
983
  (field) => field.name.value
952
984
  );
953
985
  return {
@@ -957,8 +989,7 @@ var astBuilder = {
957
989
  }
958
990
  };
959
991
  var capitalize = (s) => {
960
- if (typeof s !== "string")
961
- return "";
992
+ if (typeof s !== "string") return "";
962
993
  return s.charAt(0).toUpperCase() + s.slice(1);
963
994
  };
964
995
  var extractInlineTypes = (item) => {
@@ -1001,41 +1032,6 @@ function* walk(maybeNode, visited = /* @__PURE__ */ new WeakSet()) {
1001
1032
  yield maybeNode;
1002
1033
  visited.add(maybeNode);
1003
1034
  }
1004
- function addNamespaceToSchema(maybeNode, namespace = []) {
1005
- if (typeof maybeNode === "string") {
1006
- return maybeNode;
1007
- }
1008
- if (typeof maybeNode === "boolean") {
1009
- return maybeNode;
1010
- }
1011
- const newNode = maybeNode;
1012
- const keys = Object.keys(maybeNode);
1013
- Object.values(maybeNode).map((m, index) => {
1014
- const key = keys[index];
1015
- if (Array.isArray(m)) {
1016
- newNode[key] = m.map((element) => {
1017
- if (!element) {
1018
- return;
1019
- }
1020
- if (!element.hasOwnProperty("name")) {
1021
- return element;
1022
- }
1023
- const value = element.name || element.value;
1024
- return addNamespaceToSchema(element, [...namespace, value]);
1025
- });
1026
- } else {
1027
- if (!m) {
1028
- return;
1029
- }
1030
- if (!m.hasOwnProperty("name")) {
1031
- newNode[key] = m;
1032
- } else {
1033
- newNode[key] = addNamespaceToSchema(m, [...namespace, m.name]);
1034
- }
1035
- }
1036
- });
1037
- return { ...newNode, namespace };
1038
- }
1039
1035
  var generateNamespacedFieldName = (names, suffix = "") => {
1040
1036
  return (suffix ? [...names, suffix] : names).map(capitalize).join("");
1041
1037
  };
@@ -1195,6 +1191,11 @@ var scalarDefinitions = [
1195
1191
  required: true,
1196
1192
  type: astBuilder.TYPES.String
1197
1193
  }),
1194
+ astBuilder.FieldDefinition({
1195
+ name: "hasReferences",
1196
+ required: false,
1197
+ type: astBuilder.TYPES.Boolean
1198
+ }),
1198
1199
  astBuilder.FieldDefinition({
1199
1200
  name: "breadcrumbs",
1200
1201
  required: true,
@@ -1408,6 +1409,19 @@ var Builder = class {
1408
1409
  this.addToLookupMap = (lookup) => {
1409
1410
  this.lookupMap[lookup.type] = lookup;
1410
1411
  };
1412
+ /**
1413
+ * ```graphql
1414
+ * # ex.
1415
+ * {
1416
+ * getCollection(collection: $collection) {
1417
+ * name
1418
+ * documents {...}
1419
+ * }
1420
+ * }
1421
+ * ```
1422
+ *
1423
+ * @param collections
1424
+ */
1411
1425
  this.buildCollectionDefinition = async (collections) => {
1412
1426
  const name = "collection";
1413
1427
  const typeName = "Collection";
@@ -1478,6 +1492,19 @@ var Builder = class {
1478
1492
  required: true
1479
1493
  });
1480
1494
  };
1495
+ /**
1496
+ * ```graphql
1497
+ * # ex.
1498
+ * {
1499
+ * getCollections {
1500
+ * name
1501
+ * documents {...}
1502
+ * }
1503
+ * }
1504
+ * ```
1505
+ *
1506
+ * @param collections
1507
+ */
1481
1508
  this.buildMultiCollectionDefinition = async (collections) => {
1482
1509
  const name = "collections";
1483
1510
  const typeName = "Collection";
@@ -1488,6 +1515,17 @@ var Builder = class {
1488
1515
  required: true
1489
1516
  });
1490
1517
  };
1518
+ /**
1519
+ * ```graphql
1520
+ * # ex.
1521
+ * {
1522
+ * node(id: $id) {
1523
+ * id
1524
+ * data {...}
1525
+ * }
1526
+ * }
1527
+ * ```
1528
+ */
1491
1529
  this.multiNodeDocument = async () => {
1492
1530
  const name = "node";
1493
1531
  const args = [
@@ -1508,6 +1546,19 @@ var Builder = class {
1508
1546
  required: true
1509
1547
  });
1510
1548
  };
1549
+ /**
1550
+ * ```graphql
1551
+ * # ex.
1552
+ * {
1553
+ * getDocument(collection: $collection, relativePath: $relativePath) {
1554
+ * id
1555
+ * data {...}
1556
+ * }
1557
+ * }
1558
+ * ```
1559
+ *
1560
+ * @param collections
1561
+ */
1511
1562
  this.multiCollectionDocument = async (collections) => {
1512
1563
  const name = "document";
1513
1564
  const args = [
@@ -1533,6 +1584,19 @@ var Builder = class {
1533
1584
  required: true
1534
1585
  });
1535
1586
  };
1587
+ /**
1588
+ * ```graphql
1589
+ * # ex.
1590
+ * {
1591
+ * addPendingDocument(collection: $collection, relativePath: $relativePath, params: $params) {
1592
+ * id
1593
+ * data {...}
1594
+ * }
1595
+ * }
1596
+ * ```
1597
+ *
1598
+ * @param collections
1599
+ */
1536
1600
  this.addMultiCollectionDocumentMutation = async () => {
1537
1601
  return astBuilder.FieldDefinition({
1538
1602
  name: "addPendingDocument",
@@ -1557,6 +1621,19 @@ var Builder = class {
1557
1621
  type: astBuilder.TYPES.MultiCollectionDocument
1558
1622
  });
1559
1623
  };
1624
+ /**
1625
+ * ```graphql
1626
+ * # ex.
1627
+ * {
1628
+ * createDocument(relativePath: $relativePath, params: $params) {
1629
+ * id
1630
+ * data {...}
1631
+ * }
1632
+ * }
1633
+ * ```
1634
+ *
1635
+ * @param collections
1636
+ */
1560
1637
  this.buildCreateCollectionDocumentMutation = async (collections) => {
1561
1638
  return astBuilder.FieldDefinition({
1562
1639
  name: "createDocument",
@@ -1584,6 +1661,19 @@ var Builder = class {
1584
1661
  type: astBuilder.TYPES.MultiCollectionDocument
1585
1662
  });
1586
1663
  };
1664
+ /**
1665
+ * ```graphql
1666
+ * # ex.
1667
+ * {
1668
+ * updateDocument(relativePath: $relativePath, params: $params) {
1669
+ * id
1670
+ * data {...}
1671
+ * }
1672
+ * }
1673
+ * ```
1674
+ *
1675
+ * @param collections
1676
+ */
1587
1677
  this.buildUpdateCollectionDocumentMutation = async (collections) => {
1588
1678
  return astBuilder.FieldDefinition({
1589
1679
  name: "updateDocument",
@@ -1611,6 +1701,19 @@ var Builder = class {
1611
1701
  type: astBuilder.TYPES.MultiCollectionDocument
1612
1702
  });
1613
1703
  };
1704
+ /**
1705
+ * ```graphql
1706
+ * # ex.
1707
+ * {
1708
+ * deleteDocument(relativePath: $relativePath, params: $params) {
1709
+ * id
1710
+ * data {...}
1711
+ * }
1712
+ * }
1713
+ * ```
1714
+ *
1715
+ * @param collections
1716
+ */
1614
1717
  this.buildDeleteCollectionDocumentMutation = async (collections) => {
1615
1718
  return astBuilder.FieldDefinition({
1616
1719
  name: "deleteDocument",
@@ -1630,6 +1733,19 @@ var Builder = class {
1630
1733
  type: astBuilder.TYPES.MultiCollectionDocument
1631
1734
  });
1632
1735
  };
1736
+ /**
1737
+ * ```graphql
1738
+ * # ex.
1739
+ * {
1740
+ * createFolder(folderName: $folderName, params: $params) {
1741
+ * id
1742
+ * data {...}
1743
+ * }
1744
+ * }
1745
+ * ```
1746
+ *
1747
+ * @param collections
1748
+ */
1633
1749
  this.buildCreateCollectionFolderMutation = async () => {
1634
1750
  return astBuilder.FieldDefinition({
1635
1751
  name: "createFolder",
@@ -1649,6 +1765,19 @@ var Builder = class {
1649
1765
  type: astBuilder.TYPES.MultiCollectionDocument
1650
1766
  });
1651
1767
  };
1768
+ /**
1769
+ * ```graphql
1770
+ * # ex.
1771
+ * {
1772
+ * getPostDocument(relativePath: $relativePath) {
1773
+ * id
1774
+ * data {...}
1775
+ * }
1776
+ * }
1777
+ * ```
1778
+ *
1779
+ * @param collection
1780
+ */
1652
1781
  this.collectionDocument = async (collection) => {
1653
1782
  const name = NAMER.queryName([collection.name]);
1654
1783
  const type = await this._buildCollectionDocumentType(collection);
@@ -1709,6 +1838,20 @@ var Builder = class {
1709
1838
  const args = [];
1710
1839
  return astBuilder.FieldDefinition({ type, name, args, required: false });
1711
1840
  };
1841
+ /**
1842
+ * Turns a collection into a fragment that gets updated on build. This fragment does not resolve references
1843
+ * ```graphql
1844
+ * # ex.
1845
+ * fragment AuthorsParts on Authors {
1846
+ * name
1847
+ * avatar
1848
+ * ...
1849
+ * }
1850
+ * ```
1851
+ *
1852
+ * @public
1853
+ * @param collection a TinaCloud collection
1854
+ */
1712
1855
  this.collectionFragment = async (collection) => {
1713
1856
  const name = NAMER.dataTypeName(collection.namespace);
1714
1857
  const fragmentName = NAMER.fragmentName(collection.namespace);
@@ -1722,6 +1865,20 @@ var Builder = class {
1722
1865
  selections: filterSelections(selections)
1723
1866
  });
1724
1867
  };
1868
+ /**
1869
+ * Given a collection this function returns its selections set. For example for Post this would return
1870
+ *
1871
+ * "
1872
+ * body
1873
+ * title
1874
+ * ... on Author {
1875
+ * name
1876
+ * heroImg
1877
+ * }
1878
+ *
1879
+ * But in the AST format
1880
+ *
1881
+ * */
1725
1882
  this._getCollectionFragmentSelections = async (collection, depth) => {
1726
1883
  const selections = [];
1727
1884
  selections.push({
@@ -1803,9 +1960,9 @@ var Builder = class {
1803
1960
  ]
1804
1961
  });
1805
1962
  }
1963
+ // TODO: Should we throw here?
1806
1964
  case "reference":
1807
- if (depth >= this.maxDepth)
1808
- return false;
1965
+ if (depth >= this.maxDepth) return false;
1809
1966
  if (!("collections" in field)) {
1810
1967
  return false;
1811
1968
  }
@@ -1837,6 +1994,7 @@ var Builder = class {
1837
1994
  name: field.name,
1838
1995
  selections: [
1839
1996
  ...selections,
1997
+ // This is ... on Document { id }
1840
1998
  {
1841
1999
  kind: "InlineFragment",
1842
2000
  typeCondition: {
@@ -1867,6 +2025,19 @@ var Builder = class {
1867
2025
  });
1868
2026
  }
1869
2027
  };
2028
+ /**
2029
+ * ```graphql
2030
+ * # ex.
2031
+ * mutation {
2032
+ * updatePostDocument(relativePath: $relativePath, params: $params) {
2033
+ * id
2034
+ * data {...}
2035
+ * }
2036
+ * }
2037
+ * ```
2038
+ *
2039
+ * @param collection
2040
+ */
1870
2041
  this.updateCollectionDocumentMutation = async (collection) => {
1871
2042
  return astBuilder.FieldDefinition({
1872
2043
  type: await this._buildCollectionDocumentType(collection),
@@ -1886,6 +2057,19 @@ var Builder = class {
1886
2057
  ]
1887
2058
  });
1888
2059
  };
2060
+ /**
2061
+ * ```graphql
2062
+ * # ex.
2063
+ * mutation {
2064
+ * createPostDocument(relativePath: $relativePath, params: $params) {
2065
+ * id
2066
+ * data {...}
2067
+ * }
2068
+ * }
2069
+ * ```
2070
+ *
2071
+ * @param collection
2072
+ */
1889
2073
  this.createCollectionDocumentMutation = async (collection) => {
1890
2074
  return astBuilder.FieldDefinition({
1891
2075
  type: await this._buildCollectionDocumentType(collection),
@@ -1905,6 +2089,22 @@ var Builder = class {
1905
2089
  ]
1906
2090
  });
1907
2091
  };
2092
+ /**
2093
+ * ```graphql
2094
+ * # ex.
2095
+ * {
2096
+ * getPostList(first: 10) {
2097
+ * edges {
2098
+ * node {
2099
+ * id
2100
+ * }
2101
+ * }
2102
+ * }
2103
+ * }
2104
+ * ```
2105
+ *
2106
+ * @param collection
2107
+ */
1908
2108
  this.collectionDocumentList = async (collection) => {
1909
2109
  const connectionName = NAMER.referenceConnectionType(collection.namespace);
1910
2110
  this.addToLookupMap({
@@ -1920,6 +2120,10 @@ var Builder = class {
1920
2120
  collection
1921
2121
  });
1922
2122
  };
2123
+ /**
2124
+ * GraphQL type definitions which remain unchanged regardless
2125
+ * of the supplied Tina schema. Ex. "node" interface
2126
+ */
1923
2127
  this.buildStaticDefinitions = () => staticDefinitions;
1924
2128
  this._buildCollectionDocumentType = async (collection, suffix = "", extraFields = [], extraInterfaces = []) => {
1925
2129
  const documentTypeName = NAMER.documentTypeName(collection.namespace);
@@ -2424,6 +2628,7 @@ var Builder = class {
2424
2628
  name: NAMER.dataFilterTypeName(namespace),
2425
2629
  fields: await sequential(collections, async (collection2) => {
2426
2630
  return astBuilder.InputValueDefinition({
2631
+ // @ts-ignore
2427
2632
  name: collection2.name,
2428
2633
  type: NAMER.dataFilterTypeName(collection2.namespace)
2429
2634
  });
@@ -2509,7 +2714,7 @@ var Builder = class {
2509
2714
  this._buildDataField = async (field) => {
2510
2715
  const listWarningMsg = `
2511
2716
  WARNING: The user interface for ${field.type} does not support \`list: true\`
2512
- Visit https://tina.io/docs/errors/ui-not-supported/ for more information
2717
+ Visit https://tina.io/docs/r/content-fields/#list-fields/ for more information
2513
2718
 
2514
2719
  `;
2515
2720
  switch (field.type) {
@@ -2612,7 +2817,8 @@ Visit https://tina.io/docs/errors/ui-not-supported/ for more information
2612
2817
  ]
2613
2818
  });
2614
2819
  };
2615
- this.maxDepth = config?.tinaSchema.schema?.config?.client?.referenceDepth ?? 2;
2820
+ this.maxDepth = // @ts-ignore
2821
+ config?.tinaSchema.schema?.config?.client?.referenceDepth ?? 2;
2616
2822
  this.tinaSchema = config.tinaSchema;
2617
2823
  this.lookupMap = {};
2618
2824
  }
@@ -2623,8 +2829,7 @@ Visit https://tina.io/docs/errors/ui-not-supported/ for more information
2623
2829
  selections.push(field);
2624
2830
  });
2625
2831
  const filteredSelections = filterSelections(selections);
2626
- if (!filteredSelections.length)
2627
- return false;
2832
+ if (!filteredSelections.length) return false;
2628
2833
  return astBuilder.InlineFragmentDefinition({
2629
2834
  selections: filteredSelections,
2630
2835
  name: NAMER.dataTypeName(template.namespace)
@@ -2661,8 +2866,9 @@ var filterSelections = (arr) => {
2661
2866
  import { TinaSchema } from "@tinacms/schema-tools";
2662
2867
 
2663
2868
  // src/schema/validate.ts
2664
- import deepClone from "lodash.clonedeep";
2869
+ import { addNamespaceToSchema } from "@tinacms/schema-tools";
2665
2870
  import * as yup2 from "yup";
2871
+ import { cloneDeep } from "es-toolkit";
2666
2872
  import {
2667
2873
  validateTinaCloudSchemaConfig
2668
2874
  } from "@tinacms/schema-tools";
@@ -2679,7 +2885,7 @@ var FIELD_TYPES = [
2679
2885
  ];
2680
2886
  var validateSchema = async (schema) => {
2681
2887
  const schema2 = addNamespaceToSchema(
2682
- deepClone(schema)
2888
+ cloneDeep(schema)
2683
2889
  );
2684
2890
  const collections = await sequential(
2685
2891
  schema2.collections,
@@ -2707,6 +2913,7 @@ var validationCollectionsPathAndMatch = (collections) => {
2707
2913
  }).map((x) => `${x.path}${x.format || "md"}`);
2708
2914
  if (noMatchCollections.length !== new Set(noMatchCollections).size) {
2709
2915
  throw new Error(
2916
+ // TODO: add a link to the docs
2710
2917
  "Two collections without match can not have the same `path`. Please make the `path` unique or add a matches property to the collection."
2711
2918
  );
2712
2919
  }
@@ -2815,7 +3022,7 @@ var validateField = async (field) => {
2815
3022
  // package.json
2816
3023
  var package_default = {
2817
3024
  name: "@tinacms/graphql",
2818
- version: "1.5.5",
3025
+ version: "1.6.3",
2819
3026
  main: "dist/index.js",
2820
3027
  module: "dist/index.mjs",
2821
3028
  typings: "dist/index.d.ts",
@@ -2841,34 +3048,31 @@ var package_default = {
2841
3048
  types: "pnpm tsc",
2842
3049
  build: "tinacms-scripts build",
2843
3050
  docs: "pnpm typedoc",
2844
- serve: "pnpm nodemon dist/server.js",
2845
- test: "jest",
2846
- "test-watch": "jest --watch"
3051
+ test: "vitest run",
3052
+ "test-watch": "vitest"
2847
3053
  },
2848
3054
  dependencies: {
2849
- "@iarna/toml": "^2.2.5",
3055
+ "@iarna/toml": "catalog:",
2850
3056
  "@tinacms/mdx": "workspace:*",
2851
3057
  "@tinacms/schema-tools": "workspace:*",
2852
- "abstract-level": "^1.0.4",
3058
+ "abstract-level": "catalog:",
2853
3059
  "date-fns": "^2.30.0",
2854
- "fast-glob": "^3.3.2",
2855
- "fs-extra": "^11.2.0",
2856
- "glob-parent": "^6.0.2",
3060
+ "es-toolkit": "^1.42.0",
3061
+ "fast-glob": "catalog:",
3062
+ "fs-extra": "catalog:",
3063
+ "glob-parent": "catalog:",
2857
3064
  graphql: "15.8.0",
2858
- "gray-matter": "^4.0.3",
2859
- "isomorphic-git": "^1.27.1",
2860
- "js-sha1": "^0.6.0",
3065
+ "gray-matter": "catalog:",
3066
+ "isomorphic-git": "catalog:",
3067
+ "js-sha1": "catalog:",
2861
3068
  "js-yaml": "^3.14.1",
2862
- "jsonpath-plus": "^6.0.1",
2863
- "lodash.clonedeep": "^4.5.0",
2864
- "lodash.set": "^4.3.2",
2865
- "lodash.uniqby": "^4.7.0",
2866
- "many-level": "^2.0.0",
2867
- micromatch: "4.0.8",
2868
- "normalize-path": "^3.0.0",
2869
- "readable-stream": "^4.5.2",
2870
- scmp: "^2.1.0",
2871
- yup: "^0.32.11"
3069
+ "jsonpath-plus": "catalog:",
3070
+ "many-level": "catalog:",
3071
+ micromatch: "catalog:",
3072
+ "normalize-path": "catalog:",
3073
+ "readable-stream": "catalog:",
3074
+ scmp: "catalog:",
3075
+ yup: "^1.6.1"
2872
3076
  },
2873
3077
  publishConfig: {
2874
3078
  registry: "https://registry.npmjs.org"
@@ -2882,26 +3086,21 @@ var package_default = {
2882
3086
  "@tinacms/scripts": "workspace:*",
2883
3087
  "@types/cors": "^2.8.17",
2884
3088
  "@types/estree": "^0.0.50",
2885
- "@types/express": "^4.17.21",
3089
+ "@types/express": "catalog:",
2886
3090
  "@types/fs-extra": "^9.0.13",
2887
- "@types/jest": "^26.0.24",
2888
3091
  "@types/js-yaml": "^3.12.10",
2889
- "@types/lodash.camelcase": "^4.3.9",
2890
- "@types/lodash.upperfirst": "^4.3.9",
2891
- "@types/lru-cache": "^5.1.1",
2892
- "@types/mdast": "^3.0.15",
2893
- "@types/micromatch": "^4.0.9",
2894
- "@types/node": "^22.7.4",
2895
- "@types/normalize-path": "^3.0.2",
2896
- "@types/ws": "^7.4.7",
2897
- "@types/yup": "^0.29.14",
2898
- jest: "^29.7.0",
2899
- "jest-diff": "^29.7.0",
3092
+ "@types/lru-cache": "catalog:",
3093
+ "@types/mdast": "catalog:",
3094
+ "@types/micromatch": "catalog:",
3095
+ "@types/node": "^22.13.1",
3096
+ "@types/normalize-path": "catalog:",
3097
+ "@types/ws": "catalog:",
2900
3098
  "jest-file-snapshot": "^0.5.0",
2901
- "jest-matcher-utils": "^29.7.0",
2902
- "memory-level": "^1.0.0",
2903
- nodemon: "3.1.4",
2904
- typescript: "^5.6.2"
3099
+ "memory-level": "catalog:",
3100
+ typescript: "^5.7.3",
3101
+ vite: "^4.5.9",
3102
+ vitest: "^0.32.4",
3103
+ zod: "catalog:"
2905
3104
  }
2906
3105
  };
2907
3106
 
@@ -2973,6 +3172,7 @@ var _buildFragments = async (builder, tinaSchema) => {
2973
3172
  kind: "Document",
2974
3173
  definitions: uniqBy2(
2975
3174
  extractInlineTypes(fragmentDefinitionsFields),
3175
+ // @ts-ignore - all nodes returned by extractInlineTypes have a name property
2976
3176
  (node) => node.name.value
2977
3177
  )
2978
3178
  };
@@ -2994,6 +3194,7 @@ var _buildQueries = async (builder, tinaSchema) => {
2994
3194
  fragName,
2995
3195
  queryName: queryListName,
2996
3196
  filterType: queryFilterTypeName,
3197
+ // look for flag to see if the data layer is enabled
2997
3198
  dataLayer: Boolean(
2998
3199
  tinaSchema.config?.meta?.flags?.find((x) => x === "experimentalData")
2999
3200
  )
@@ -3004,6 +3205,7 @@ var _buildQueries = async (builder, tinaSchema) => {
3004
3205
  kind: "Document",
3005
3206
  definitions: uniqBy2(
3006
3207
  extractInlineTypes(operationsDefinitions),
3208
+ // @ts-ignore - all nodes returned by extractInlineTypes have a name property
3007
3209
  (node) => node.name.value
3008
3210
  )
3009
3211
  };
@@ -3054,7 +3256,9 @@ var _buildSchema = async (builder, tinaSchema) => {
3054
3256
  await builder.buildCreateCollectionFolderMutation()
3055
3257
  );
3056
3258
  await sequential(collections, async (collection) => {
3057
- queryTypeDefinitionFields.push(await builder.collectionDocument(collection));
3259
+ queryTypeDefinitionFields.push(
3260
+ await builder.collectionDocument(collection)
3261
+ );
3058
3262
  if (collection.isAuthCollection) {
3059
3263
  queryTypeDefinitionFields.push(
3060
3264
  await builder.authenticationCollectionDocument(collection)
@@ -3088,13 +3292,15 @@ var _buildSchema = async (builder, tinaSchema) => {
3088
3292
  fields: mutationTypeDefinitionFields
3089
3293
  })
3090
3294
  );
3091
- return {
3295
+ const schema = {
3092
3296
  kind: "Document",
3093
3297
  definitions: uniqBy2(
3094
3298
  extractInlineTypes(definitions),
3299
+ // @ts-ignore - all nodes returned by extractInlineTypes have a name property
3095
3300
  (node) => node.name.value
3096
3301
  )
3097
3302
  };
3303
+ return schema;
3098
3304
  };
3099
3305
 
3100
3306
  // src/resolve.ts
@@ -3103,382 +3309,146 @@ import { graphql, buildASTSchema, getNamedType, GraphQLError as GraphQLError4 }
3103
3309
  // src/resolver/index.ts
3104
3310
  import path3 from "path";
3105
3311
  import isValid from "date-fns/isValid/index.js";
3312
+ import { JSONPath as JSONPath2 } from "jsonpath-plus";
3106
3313
 
3107
3314
  // src/mdx/index.ts
3108
- import { parseMDX, stringifyMDX } from "@tinacms/mdx";
3315
+ import { parseMDX, serializeMDX } from "@tinacms/mdx";
3109
3316
 
3110
- // src/resolver/error.ts
3111
- var TinaGraphQLError = class extends Error {
3112
- constructor(message, extensions) {
3113
- super(message);
3114
- if (!this.name) {
3115
- Object.defineProperty(this, "name", { value: "TinaGraphQLError" });
3116
- }
3117
- this.extensions = { ...extensions };
3118
- }
3317
+ // src/resolver/index.ts
3318
+ import { GraphQLError as GraphQLError2 } from "graphql";
3319
+
3320
+ // src/database/datalayer.ts
3321
+ import { JSONPath } from "jsonpath-plus";
3322
+ import sha from "js-sha1";
3323
+
3324
+ // src/database/level.ts
3325
+ var ARRAY_ITEM_VALUE_SEPARATOR = ",";
3326
+ var INDEX_KEY_FIELD_SEPARATOR = "";
3327
+ var CONTENT_ROOT_PREFIX = "~";
3328
+ var SUBLEVEL_OPTIONS = {
3329
+ separator: INDEX_KEY_FIELD_SEPARATOR,
3330
+ valueEncoding: "json"
3119
3331
  };
3120
- var TinaFetchError = class extends Error {
3121
- constructor(message, args) {
3122
- super(message);
3123
- this.name = "TinaFetchError";
3124
- this.collection = args.collection;
3125
- this.stack = args.stack;
3126
- this.file = args.file;
3127
- this.originalError = args.originalError;
3332
+ var LevelProxyHandler = {
3333
+ get: function(target, property) {
3334
+ if (!target[property]) {
3335
+ throw new Error(`The property, ${property.toString()}, doesn't exist`);
3336
+ }
3337
+ if (typeof target[property] !== "function") {
3338
+ throw new Error(
3339
+ `The property, ${property.toString()}, is not a function`
3340
+ );
3341
+ }
3342
+ if (property === "get") {
3343
+ return async (...args) => {
3344
+ let result;
3345
+ try {
3346
+ result = await target[property].apply(target, args);
3347
+ } catch (e) {
3348
+ if (e.code !== "LEVEL_NOT_FOUND") {
3349
+ throw e;
3350
+ }
3351
+ }
3352
+ return result;
3353
+ };
3354
+ } else if (property === "sublevel") {
3355
+ return (...args) => {
3356
+ return new Proxy(
3357
+ // eslint-disable-next-line prefer-spread
3358
+ target[property].apply(target, args),
3359
+ LevelProxyHandler
3360
+ );
3361
+ };
3362
+ } else {
3363
+ return (...args) => target[property].apply(target, args);
3364
+ }
3128
3365
  }
3129
3366
  };
3130
- var TinaQueryError = class extends TinaFetchError {
3131
- constructor(args) {
3132
- super(
3133
- `Error querying file ${args.file} from collection ${args.collection}. ${auditMessage(args.includeAuditMessage)}`,
3134
- args
3135
- );
3367
+ var LevelProxy = class {
3368
+ constructor(level) {
3369
+ return new Proxy(level, LevelProxyHandler);
3136
3370
  }
3137
3371
  };
3138
- var TinaParseDocumentError = class extends TinaFetchError {
3139
- constructor(args) {
3140
- super(
3141
- `Error parsing file ${args.file} from collection ${args.collection}. ${auditMessage(args.includeAuditMessage)}`,
3142
- args
3372
+
3373
+ // src/database/datalayer.ts
3374
+ import path2 from "path";
3375
+
3376
+ // src/database/util.ts
3377
+ import toml from "@iarna/toml";
3378
+ import {
3379
+ normalizePath
3380
+ } from "@tinacms/schema-tools";
3381
+ import matter from "gray-matter";
3382
+ import yaml from "js-yaml";
3383
+ import path from "path";
3384
+ import micromatch from "micromatch";
3385
+
3386
+ // src/database/alias-utils.ts
3387
+ var replaceBlockAliases = (template, item) => {
3388
+ const output = { ...item };
3389
+ const templateKey = template.templateKey || "_template";
3390
+ const templateName = output[templateKey];
3391
+ const matchingTemplate = template.templates.find(
3392
+ (t) => t.nameOverride == templateName || t.name == templateName
3393
+ );
3394
+ if (!matchingTemplate) {
3395
+ throw new Error(
3396
+ `Block template "${templateName}" is not defined for field "${template.name}"`
3143
3397
  );
3144
3398
  }
3145
- toString() {
3146
- return super.toString() + "\n OriginalError: \n" + this.originalError.toString();
3399
+ output._template = matchingTemplate.name;
3400
+ if (templateKey != "_template") {
3401
+ delete output[templateKey];
3147
3402
  }
3403
+ return output;
3148
3404
  };
3149
- var auditMessage = (includeAuditMessage = true) => includeAuditMessage ? `Please run "tinacms audit" or add the --verbose option for more info` : "";
3150
- var handleFetchErrorError = (e, verbose) => {
3151
- if (e instanceof Error) {
3152
- if (e instanceof TinaFetchError) {
3153
- if (verbose) {
3154
- console.log(e.toString());
3155
- console.log(e);
3156
- console.log(e.stack);
3405
+ var replaceNameOverrides = (template, obj) => {
3406
+ if (template.list) {
3407
+ return obj.map((item) => {
3408
+ if (isBlockField(template)) {
3409
+ item = replaceBlockAliases(template, item);
3157
3410
  }
3158
- }
3411
+ return _replaceNameOverrides(
3412
+ getTemplateForData(template, item).fields,
3413
+ item
3414
+ );
3415
+ });
3159
3416
  } else {
3160
- console.error(e);
3417
+ return _replaceNameOverrides(getTemplateForData(template, obj).fields, obj);
3161
3418
  }
3162
- throw e;
3163
3419
  };
3164
-
3165
- // src/resolver/filter-utils.ts
3166
- var resolveReferences = async (filter, fields, resolver) => {
3167
- for (const fieldKey of Object.keys(filter)) {
3168
- const fieldDefinition = fields.find(
3169
- (f) => f.name === fieldKey
3420
+ function isBlockField(field) {
3421
+ return field && field.type === "object" && field.templates?.length > 0;
3422
+ }
3423
+ var _replaceNameOverrides = (fields, obj) => {
3424
+ const output = {};
3425
+ Object.keys(obj).forEach((key) => {
3426
+ const field = fields.find(
3427
+ (fieldWithMatchingAlias) => (fieldWithMatchingAlias?.nameOverride || fieldWithMatchingAlias?.name) === key
3170
3428
  );
3171
- if (fieldDefinition) {
3172
- if (fieldDefinition.type === "reference") {
3173
- const { edges, values } = await resolver(filter, fieldDefinition);
3174
- if (edges.length === 1) {
3175
- filter[fieldKey] = {
3176
- eq: values[0]
3177
- };
3178
- } else if (edges.length > 1) {
3179
- filter[fieldKey] = {
3180
- in: values
3181
- };
3182
- } else {
3183
- filter[fieldKey] = {
3184
- eq: "___null___"
3185
- };
3186
- }
3187
- } else if (fieldDefinition.type === "object") {
3188
- if (fieldDefinition.templates) {
3189
- for (const templateName of Object.keys(filter[fieldKey])) {
3190
- const template = fieldDefinition.templates.find(
3191
- (template2) => !(typeof template2 === "string") && template2.name === templateName
3192
- );
3193
- if (template) {
3194
- await resolveReferences(
3195
- filter[fieldKey][templateName],
3196
- template.fields,
3197
- resolver
3198
- );
3199
- } else {
3200
- throw new Error(`Template ${templateName} not found`);
3201
- }
3202
- }
3203
- } else {
3204
- await resolveReferences(
3205
- filter[fieldKey],
3206
- fieldDefinition.fields,
3207
- resolver
3208
- );
3209
- }
3210
- }
3211
- } else {
3212
- throw new Error(`Unable to find field ${fieldKey}`);
3213
- }
3214
- }
3429
+ output[field?.name || key] = field?.type == "object" ? replaceNameOverrides(field, obj[key]) : obj[key];
3430
+ });
3431
+ return output;
3215
3432
  };
3216
- var collectConditionsForChildFields = (filterNode, fields, pathExpression, collectCondition) => {
3217
- for (const childFieldName of Object.keys(filterNode)) {
3218
- const childField = fields.find((field) => field.name === childFieldName);
3219
- if (!childField) {
3220
- throw new Error(`Unable to find type for field ${childFieldName}`);
3433
+ var getTemplateForData = (field, data) => {
3434
+ if (field.templates?.length) {
3435
+ const templateKey = "_template";
3436
+ if (data[templateKey]) {
3437
+ const result = field.templates.find(
3438
+ (template) => template.nameOverride === data[templateKey] || template.name === data[templateKey]
3439
+ );
3440
+ if (result) {
3441
+ return result;
3442
+ }
3443
+ throw new Error(
3444
+ `Template "${data[templateKey]}" is not defined for field "${field.name}"`
3445
+ );
3221
3446
  }
3222
- collectConditionsForField(
3223
- childFieldName,
3224
- childField,
3225
- filterNode[childFieldName],
3226
- pathExpression,
3227
- collectCondition
3447
+ throw new Error(
3448
+ `Missing required key "${templateKey}" on field "${field.name}"`
3228
3449
  );
3229
- }
3230
- };
3231
- var collectConditionsForObjectField = (fieldName, field, filterNode, pathExpression, collectCondition) => {
3232
- if (field.list && field.templates) {
3233
- for (const [filterKey, childFilterNode] of Object.entries(filterNode)) {
3234
- const template = field.templates.find(
3235
- (template2) => !(typeof template2 === "string") && template2.name === filterKey
3236
- );
3237
- const jsonPath = `${fieldName}[?(@._template=="${filterKey}")]`;
3238
- const filterPath = pathExpression ? `${pathExpression}.${jsonPath}` : jsonPath;
3239
- collectConditionsForChildFields(
3240
- childFilterNode,
3241
- template.fields,
3242
- filterPath,
3243
- collectCondition
3244
- );
3245
- }
3246
- } else {
3247
- const jsonPath = `${fieldName}${field.list ? "[*]" : ""}`;
3248
- const filterPath = pathExpression ? `${pathExpression}.${jsonPath}` : `${jsonPath}`;
3249
- collectConditionsForChildFields(
3250
- filterNode,
3251
- field.fields,
3252
- filterPath,
3253
- collectCondition
3254
- );
3255
- }
3256
- };
3257
- var collectConditionsForField = (fieldName, field, filterNode, pathExpression, collectCondition) => {
3258
- if (field.type === "object") {
3259
- collectConditionsForObjectField(
3260
- fieldName,
3261
- field,
3262
- filterNode,
3263
- pathExpression,
3264
- collectCondition
3265
- );
3266
- } else {
3267
- collectCondition({
3268
- filterPath: pathExpression ? `${pathExpression}.${fieldName}` : fieldName,
3269
- filterExpression: {
3270
- _type: field.type,
3271
- _list: !!field.list,
3272
- ...filterNode
3273
- }
3274
- });
3275
- }
3276
- };
3277
-
3278
- // src/resolver/media-utils.ts
3279
- var resolveMediaCloudToRelative = (value, config = { useRelativeMedia: true }, schema) => {
3280
- if (config && value) {
3281
- if (config.useRelativeMedia === true) {
3282
- return value;
3283
- }
3284
- if (hasTinaMediaConfig(schema) === true) {
3285
- const assetsURL = `https://${config.assetsHost}/${config.clientId}`;
3286
- if (typeof value === "string" && value.includes(assetsURL)) {
3287
- const cleanMediaRoot = cleanUpSlashes(
3288
- schema.config.media.tina.mediaRoot
3289
- );
3290
- const strippedURL = value.replace(assetsURL, "");
3291
- return `${cleanMediaRoot}${strippedURL}`;
3292
- }
3293
- if (Array.isArray(value)) {
3294
- return value.map((v) => {
3295
- if (!v || typeof v !== "string")
3296
- return v;
3297
- const cleanMediaRoot = cleanUpSlashes(
3298
- schema.config.media.tina.mediaRoot
3299
- );
3300
- const strippedURL = v.replace(assetsURL, "");
3301
- return `${cleanMediaRoot}${strippedURL}`;
3302
- });
3303
- }
3304
- return value;
3305
- }
3306
- return value;
3307
- } else {
3308
- return value;
3309
- }
3310
- };
3311
- var resolveMediaRelativeToCloud = (value, config = { useRelativeMedia: true }, schema) => {
3312
- if (config && value) {
3313
- if (config.useRelativeMedia === true) {
3314
- return value;
3315
- }
3316
- if (hasTinaMediaConfig(schema) === true) {
3317
- const cleanMediaRoot = cleanUpSlashes(schema.config.media.tina.mediaRoot);
3318
- if (typeof value === "string") {
3319
- const strippedValue = value.replace(cleanMediaRoot, "");
3320
- return `https://${config.assetsHost}/${config.clientId}${strippedValue}`;
3321
- }
3322
- if (Array.isArray(value)) {
3323
- return value.map((v) => {
3324
- if (!v || typeof v !== "string")
3325
- return v;
3326
- const strippedValue = v.replace(cleanMediaRoot, "");
3327
- return `https://${config.assetsHost}/${config.clientId}${strippedValue}`;
3328
- });
3329
- }
3330
- }
3331
- return value;
3332
- } else {
3333
- return value;
3334
- }
3335
- };
3336
- var cleanUpSlashes = (path7) => {
3337
- if (path7) {
3338
- return `/${path7.replace(/^\/+|\/+$/gm, "")}`;
3339
- }
3340
- return "";
3341
- };
3342
- var hasTinaMediaConfig = (schema) => {
3343
- if (!schema.config?.media?.tina)
3344
- return false;
3345
- if (typeof schema.config?.media?.tina?.publicFolder !== "string" && typeof schema.config?.media?.tina?.mediaRoot !== "string")
3346
- return false;
3347
- return true;
3348
- };
3349
-
3350
- // src/resolver/index.ts
3351
- import { GraphQLError as GraphQLError2 } from "graphql";
3352
-
3353
- // src/database/datalayer.ts
3354
- import { JSONPath } from "jsonpath-plus";
3355
- import sha from "js-sha1";
3356
-
3357
- // src/database/level.ts
3358
- var ARRAY_ITEM_VALUE_SEPARATOR = ",";
3359
- var INDEX_KEY_FIELD_SEPARATOR = "";
3360
- var CONTENT_ROOT_PREFIX = "~";
3361
- var SUBLEVEL_OPTIONS = {
3362
- separator: INDEX_KEY_FIELD_SEPARATOR,
3363
- valueEncoding: "json"
3364
- };
3365
- var LevelProxyHandler = {
3366
- get: function(target, property) {
3367
- if (!target[property]) {
3368
- throw new Error(`The property, ${property.toString()}, doesn't exist`);
3369
- }
3370
- if (typeof target[property] !== "function") {
3371
- throw new Error(`The property, ${property.toString()}, is not a function`);
3372
- }
3373
- if (property === "get") {
3374
- return async (...args) => {
3375
- let result;
3376
- try {
3377
- result = await target[property].apply(target, args);
3378
- } catch (e) {
3379
- if (e.code !== "LEVEL_NOT_FOUND") {
3380
- throw e;
3381
- }
3382
- }
3383
- return result;
3384
- };
3385
- } else if (property === "sublevel") {
3386
- return (...args) => {
3387
- return new Proxy(
3388
- target[property].apply(target, args),
3389
- LevelProxyHandler
3390
- );
3391
- };
3392
- } else {
3393
- return (...args) => target[property].apply(target, args);
3394
- }
3395
- }
3396
- };
3397
- var LevelProxy = class {
3398
- constructor(level) {
3399
- return new Proxy(level, LevelProxyHandler);
3400
- }
3401
- };
3402
-
3403
- // src/database/datalayer.ts
3404
- import path2 from "path";
3405
-
3406
- // src/database/util.ts
3407
- import toml from "@iarna/toml";
3408
- import yaml from "js-yaml";
3409
- import matter from "gray-matter";
3410
- import {
3411
- normalizePath
3412
- } from "@tinacms/schema-tools";
3413
- import micromatch from "micromatch";
3414
- import path from "path";
3415
-
3416
- // src/database/alias-utils.ts
3417
- var replaceBlockAliases = (template, item) => {
3418
- const output = { ...item };
3419
- const templateKey = template.templateKey || "_template";
3420
- const templateName = output[templateKey];
3421
- const matchingTemplate = template.templates.find(
3422
- (t) => t.nameOverride == templateName || t.name == templateName
3423
- );
3424
- if (!matchingTemplate) {
3425
- throw new Error(
3426
- `Block template "${templateName}" is not defined for field "${template.name}"`
3427
- );
3428
- }
3429
- output._template = matchingTemplate.name;
3430
- if (templateKey != "_template") {
3431
- delete output[templateKey];
3432
- }
3433
- return output;
3434
- };
3435
- var replaceNameOverrides = (template, obj) => {
3436
- if (template.list) {
3437
- return obj.map((item) => {
3438
- if (isBlockField(template)) {
3439
- item = replaceBlockAliases(template, item);
3440
- }
3441
- return _replaceNameOverrides(
3442
- getTemplateForData(template, item).fields,
3443
- item
3444
- );
3445
- });
3446
- } else {
3447
- return _replaceNameOverrides(getTemplateForData(template, obj).fields, obj);
3448
- }
3449
- };
3450
- function isBlockField(field) {
3451
- return field && field.type === "object" && field.templates?.length > 0;
3452
- }
3453
- var _replaceNameOverrides = (fields, obj) => {
3454
- const output = {};
3455
- Object.keys(obj).forEach((key) => {
3456
- const field = fields.find(
3457
- (fieldWithMatchingAlias) => (fieldWithMatchingAlias?.nameOverride || fieldWithMatchingAlias?.name) === key
3458
- );
3459
- output[field?.name || key] = field?.type == "object" ? replaceNameOverrides(field, obj[key]) : obj[key];
3460
- });
3461
- return output;
3462
- };
3463
- var getTemplateForData = (field, data) => {
3464
- if (field.templates?.length) {
3465
- const templateKey = "_template";
3466
- if (data[templateKey]) {
3467
- const result = field.templates.find(
3468
- (template) => template.nameOverride === data[templateKey] || template.name === data[templateKey]
3469
- );
3470
- if (result) {
3471
- return result;
3472
- }
3473
- throw new Error(
3474
- `Template "${data[templateKey]}" is not defined for field "${field.name}"`
3475
- );
3476
- }
3477
- throw new Error(
3478
- `Missing required key "${templateKey}" on field "${field.name}"`
3479
- );
3480
- } else {
3481
- return field;
3450
+ } else {
3451
+ return field;
3482
3452
  }
3483
3453
  };
3484
3454
  var applyBlockAliases = (template, item) => {
@@ -3752,6 +3722,9 @@ var loadAndParseWithAliases = async (bridge, filepath, collection, templateInfo)
3752
3722
 
3753
3723
  // src/database/datalayer.ts
3754
3724
  var DEFAULT_COLLECTION_SORT_KEY = "__filepath__";
3725
+ var REFS_COLLECTIONS_SORT_KEY = "__refs__";
3726
+ var REFS_REFERENCE_FIELD = "__tina_ref__";
3727
+ var REFS_PATH_FIELD = "__tina_ref_path__";
3755
3728
  var DEFAULT_NUMERIC_LPAD = 4;
3756
3729
  var applyPadding = (input, pad) => {
3757
3730
  if (pad) {
@@ -4261,6 +4234,7 @@ var makeFolderOpsForCollection = (folderTree, collection, indexDefinitions, opTy
4261
4234
  result.push({
4262
4235
  type: opType,
4263
4236
  key: `${collection.path}/${subFolderKey}.${collection.format}`,
4237
+ // replace the root with the collection path
4264
4238
  sublevel: indexSublevel,
4265
4239
  value: {}
4266
4240
  });
@@ -4324,6 +4298,57 @@ var makeIndexOpsForDocument = (filepath, collection, indexDefinitions, data, opT
4324
4298
  }
4325
4299
  return result;
4326
4300
  };
4301
+ var makeRefOpsForDocument = (filepath, collection, references, data, opType, level) => {
4302
+ const result = [];
4303
+ if (collection) {
4304
+ for (const [c, referencePaths] of Object.entries(references || {})) {
4305
+ if (!referencePaths.length) {
4306
+ continue;
4307
+ }
4308
+ const collectionSublevel = level.sublevel(c, SUBLEVEL_OPTIONS);
4309
+ const refSublevel = collectionSublevel.sublevel(
4310
+ REFS_COLLECTIONS_SORT_KEY,
4311
+ SUBLEVEL_OPTIONS
4312
+ );
4313
+ const references2 = {};
4314
+ for (const path7 of referencePaths) {
4315
+ const ref = JSONPath({ path: path7, json: data });
4316
+ if (!ref) {
4317
+ continue;
4318
+ }
4319
+ if (Array.isArray(ref)) {
4320
+ for (const r of ref) {
4321
+ if (!r) {
4322
+ continue;
4323
+ }
4324
+ if (references2[r]) {
4325
+ references2[r].push(path7);
4326
+ } else {
4327
+ references2[r] = [path7];
4328
+ }
4329
+ }
4330
+ } else {
4331
+ if (references2[ref]) {
4332
+ references2[ref].push(path7);
4333
+ } else {
4334
+ references2[ref] = [path7];
4335
+ }
4336
+ }
4337
+ }
4338
+ for (const ref of Object.keys(references2)) {
4339
+ for (const path7 of references2[ref]) {
4340
+ result.push({
4341
+ type: opType,
4342
+ key: `${ref}${INDEX_KEY_FIELD_SEPARATOR}${path7}${INDEX_KEY_FIELD_SEPARATOR}${filepath}`,
4343
+ sublevel: refSublevel,
4344
+ value: opType === "put" ? {} : void 0
4345
+ });
4346
+ }
4347
+ }
4348
+ }
4349
+ }
4350
+ return result;
4351
+ };
4327
4352
  var makeStringEscaper = (regex, replacement) => {
4328
4353
  return (input) => {
4329
4354
  if (Array.isArray(input)) {
@@ -4339,10 +4364,246 @@ var makeStringEscaper = (regex, replacement) => {
4339
4364
  }
4340
4365
  };
4341
4366
  };
4342
- var stringEscaper = makeStringEscaper(
4343
- new RegExp(INDEX_KEY_FIELD_SEPARATOR, "gm"),
4344
- encodeURIComponent(INDEX_KEY_FIELD_SEPARATOR)
4345
- );
4367
+ var stringEscaper = makeStringEscaper(
4368
+ new RegExp(INDEX_KEY_FIELD_SEPARATOR, "gm"),
4369
+ encodeURIComponent(INDEX_KEY_FIELD_SEPARATOR)
4370
+ );
4371
+
4372
+ // src/resolver/error.ts
4373
+ var TinaGraphQLError = class extends Error {
4374
+ constructor(message, extensions) {
4375
+ super(message);
4376
+ if (!this.name) {
4377
+ Object.defineProperty(this, "name", { value: "TinaGraphQLError" });
4378
+ }
4379
+ this.extensions = { ...extensions };
4380
+ }
4381
+ };
4382
+ var TinaFetchError = class extends Error {
4383
+ constructor(message, args) {
4384
+ super(message);
4385
+ this.name = "TinaFetchError";
4386
+ this.collection = args.collection;
4387
+ this.file = args.file;
4388
+ this.originalError = args.originalError;
4389
+ }
4390
+ };
4391
+ var TinaQueryError = class extends TinaFetchError {
4392
+ constructor(args) {
4393
+ super(
4394
+ `Error querying file ${args.file} from collection ${args.collection}. ${auditMessage(args.includeAuditMessage)}`,
4395
+ args
4396
+ );
4397
+ }
4398
+ };
4399
+ var TinaParseDocumentError = class extends TinaFetchError {
4400
+ constructor(args) {
4401
+ super(
4402
+ `Error parsing file ${args.file} from collection ${args.collection}. ${auditMessage(args.includeAuditMessage)}`,
4403
+ args
4404
+ );
4405
+ }
4406
+ toString() {
4407
+ return super.toString() + "\n OriginalError: \n" + this.originalError.toString();
4408
+ }
4409
+ };
4410
+ var auditMessage = (includeAuditMessage = true) => includeAuditMessage ? `Please run "tinacms audit" or add the --verbose option for more info` : "";
4411
+ var handleFetchErrorError = (e, verbose) => {
4412
+ if (e instanceof Error) {
4413
+ if (e instanceof TinaFetchError) {
4414
+ if (verbose) {
4415
+ console.log(e.toString());
4416
+ console.log(e);
4417
+ console.log(e.stack);
4418
+ }
4419
+ }
4420
+ } else {
4421
+ console.error(e);
4422
+ }
4423
+ throw e;
4424
+ };
4425
+
4426
+ // src/resolver/filter-utils.ts
4427
+ var resolveReferences = async (filter, fields, resolver) => {
4428
+ for (const fieldKey of Object.keys(filter)) {
4429
+ const fieldDefinition = fields.find(
4430
+ (f) => f.name === fieldKey
4431
+ );
4432
+ if (fieldDefinition) {
4433
+ if (fieldDefinition.type === "reference") {
4434
+ const { edges, values } = await resolver(filter, fieldDefinition);
4435
+ if (edges.length === 1) {
4436
+ filter[fieldKey] = {
4437
+ eq: values[0]
4438
+ };
4439
+ } else if (edges.length > 1) {
4440
+ filter[fieldKey] = {
4441
+ in: values
4442
+ };
4443
+ } else {
4444
+ filter[fieldKey] = {
4445
+ eq: "___null___"
4446
+ };
4447
+ }
4448
+ } else if (fieldDefinition.type === "object") {
4449
+ if (fieldDefinition.templates) {
4450
+ for (const templateName of Object.keys(filter[fieldKey])) {
4451
+ const template = fieldDefinition.templates.find(
4452
+ (template2) => !(typeof template2 === "string") && template2.name === templateName
4453
+ );
4454
+ if (template) {
4455
+ await resolveReferences(
4456
+ filter[fieldKey][templateName],
4457
+ template.fields,
4458
+ resolver
4459
+ );
4460
+ } else {
4461
+ throw new Error(`Template ${templateName} not found`);
4462
+ }
4463
+ }
4464
+ } else {
4465
+ await resolveReferences(
4466
+ filter[fieldKey],
4467
+ fieldDefinition.fields,
4468
+ resolver
4469
+ );
4470
+ }
4471
+ }
4472
+ } else {
4473
+ throw new Error(`Unable to find field ${fieldKey}`);
4474
+ }
4475
+ }
4476
+ };
4477
+ var collectConditionsForChildFields = (filterNode, fields, pathExpression, collectCondition) => {
4478
+ for (const childFieldName of Object.keys(filterNode)) {
4479
+ const childField = fields.find((field) => field.name === childFieldName);
4480
+ if (!childField) {
4481
+ throw new Error(`Unable to find type for field ${childFieldName}`);
4482
+ }
4483
+ collectConditionsForField(
4484
+ childFieldName,
4485
+ childField,
4486
+ filterNode[childFieldName],
4487
+ pathExpression,
4488
+ collectCondition
4489
+ );
4490
+ }
4491
+ };
4492
+ var collectConditionsForObjectField = (fieldName, field, filterNode, pathExpression, collectCondition) => {
4493
+ if (field.list && field.templates) {
4494
+ for (const [filterKey, childFilterNode] of Object.entries(filterNode)) {
4495
+ const template = field.templates.find(
4496
+ (template2) => !(typeof template2 === "string") && template2.name === filterKey
4497
+ );
4498
+ const jsonPath = `${fieldName}[?(@._template=="${filterKey}")]`;
4499
+ const filterPath = pathExpression ? `${pathExpression}.${jsonPath}` : jsonPath;
4500
+ collectConditionsForChildFields(
4501
+ childFilterNode,
4502
+ template.fields,
4503
+ filterPath,
4504
+ collectCondition
4505
+ );
4506
+ }
4507
+ } else {
4508
+ const jsonPath = `${fieldName}${field.list ? "[*]" : ""}`;
4509
+ const filterPath = pathExpression ? `${pathExpression}.${jsonPath}` : `${jsonPath}`;
4510
+ collectConditionsForChildFields(
4511
+ filterNode,
4512
+ field.fields,
4513
+ filterPath,
4514
+ collectCondition
4515
+ );
4516
+ }
4517
+ };
4518
+ var collectConditionsForField = (fieldName, field, filterNode, pathExpression, collectCondition) => {
4519
+ if (field.type === "object") {
4520
+ collectConditionsForObjectField(
4521
+ fieldName,
4522
+ field,
4523
+ filterNode,
4524
+ pathExpression,
4525
+ collectCondition
4526
+ );
4527
+ } else {
4528
+ collectCondition({
4529
+ filterPath: pathExpression ? `${pathExpression}.${fieldName}` : fieldName,
4530
+ filterExpression: {
4531
+ _type: field.type,
4532
+ _list: !!field.list,
4533
+ ...filterNode
4534
+ }
4535
+ });
4536
+ }
4537
+ };
4538
+
4539
+ // src/resolver/media-utils.ts
4540
+ var resolveMediaCloudToRelative = (value, config = { useRelativeMedia: true }, schema) => {
4541
+ if (config && value) {
4542
+ if (config.useRelativeMedia === true) {
4543
+ return value;
4544
+ }
4545
+ if (hasTinaMediaConfig(schema) === true) {
4546
+ const assetsURL = `https://${config.assetsHost}/${config.clientId}`;
4547
+ if (typeof value === "string" && value.includes(assetsURL)) {
4548
+ const cleanMediaRoot = cleanUpSlashes(
4549
+ schema.config.media.tina.mediaRoot
4550
+ );
4551
+ const strippedURL = value.replace(assetsURL, "");
4552
+ return `${cleanMediaRoot}${strippedURL}`;
4553
+ }
4554
+ if (Array.isArray(value)) {
4555
+ return value.map((v) => {
4556
+ if (!v || typeof v !== "string") return v;
4557
+ const cleanMediaRoot = cleanUpSlashes(
4558
+ schema.config.media.tina.mediaRoot
4559
+ );
4560
+ const strippedURL = v.replace(assetsURL, "");
4561
+ return `${cleanMediaRoot}${strippedURL}`;
4562
+ });
4563
+ }
4564
+ return value;
4565
+ }
4566
+ return value;
4567
+ } else {
4568
+ return value;
4569
+ }
4570
+ };
4571
+ var resolveMediaRelativeToCloud = (value, config = { useRelativeMedia: true }, schema) => {
4572
+ if (config && value) {
4573
+ if (config.useRelativeMedia === true) {
4574
+ return value;
4575
+ }
4576
+ if (hasTinaMediaConfig(schema) === true) {
4577
+ const cleanMediaRoot = cleanUpSlashes(schema.config.media.tina.mediaRoot);
4578
+ if (typeof value === "string") {
4579
+ const strippedValue = value.replace(cleanMediaRoot, "");
4580
+ return `https://${config.assetsHost}/${config.clientId}${strippedValue}`;
4581
+ }
4582
+ if (Array.isArray(value)) {
4583
+ return value.map((v) => {
4584
+ if (!v || typeof v !== "string") return v;
4585
+ const strippedValue = v.replace(cleanMediaRoot, "");
4586
+ return `https://${config.assetsHost}/${config.clientId}${strippedValue}`;
4587
+ });
4588
+ }
4589
+ }
4590
+ return value;
4591
+ } else {
4592
+ return value;
4593
+ }
4594
+ };
4595
+ var cleanUpSlashes = (path7) => {
4596
+ if (path7) {
4597
+ return `/${path7.replace(/^\/+|\/+$/gm, "")}`;
4598
+ }
4599
+ return "";
4600
+ };
4601
+ var hasTinaMediaConfig = (schema) => {
4602
+ if (!schema.config?.media?.tina) return false;
4603
+ if (typeof schema.config?.media?.tina?.publicFolder !== "string" && typeof schema.config?.media?.tina?.mediaRoot !== "string")
4604
+ return false;
4605
+ return true;
4606
+ };
4346
4607
 
4347
4608
  // src/resolver/index.ts
4348
4609
  var createResolver = (args) => {
@@ -4375,6 +4636,7 @@ var resolveFieldData = async ({ namespace, ...field }, rawData, accumulator, tin
4375
4636
  case "password":
4376
4637
  accumulator[field.name] = {
4377
4638
  value: void 0,
4639
+ // never resolve the password hash
4378
4640
  passwordChangeRequired: value["passwordChangeRequired"] ?? false
4379
4641
  };
4380
4642
  break;
@@ -4469,7 +4731,7 @@ var resolveFieldData = async ({ namespace, ...field }, rawData, accumulator, tin
4469
4731
  }
4470
4732
  return accumulator;
4471
4733
  };
4472
- var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config, isAudit) => {
4734
+ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config, isAudit, hasReferences) => {
4473
4735
  const collection = tinaSchema.getCollection(rawData._collection);
4474
4736
  try {
4475
4737
  const template = tinaSchema.getTemplateForData({
@@ -4503,8 +4765,7 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
4503
4765
  originalError: e,
4504
4766
  collection: collection.name,
4505
4767
  includeAuditMessage: !isAudit,
4506
- file: relativePath,
4507
- stack: e.stack
4768
+ file: relativePath
4508
4769
  });
4509
4770
  }
4510
4771
  const titleField = template.fields.find((x) => {
@@ -4523,6 +4784,7 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
4523
4784
  basename,
4524
4785
  filename,
4525
4786
  extension,
4787
+ hasReferences,
4526
4788
  path: fullPath,
4527
4789
  relativePath,
4528
4790
  breadcrumbs,
@@ -4542,6 +4804,34 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
4542
4804
  throw e;
4543
4805
  }
4544
4806
  };
4807
+ var updateObjectWithJsonPath = (obj, path7, oldValue, newValue) => {
4808
+ let updated = false;
4809
+ if (!path7.includes(".") && !path7.includes("[")) {
4810
+ if (path7 in obj && obj[path7] === oldValue) {
4811
+ obj[path7] = newValue;
4812
+ updated = true;
4813
+ }
4814
+ return { object: obj, updated };
4815
+ }
4816
+ const parentPath = path7.replace(/\.[^.\[\]]+$/, "");
4817
+ const keyToUpdate = path7.match(/[^.\[\]]+$/)[0];
4818
+ const parents = JSONPath2({
4819
+ path: parentPath,
4820
+ json: obj,
4821
+ resultType: "value"
4822
+ });
4823
+ if (parents.length > 0) {
4824
+ parents.forEach((parent) => {
4825
+ if (parent && typeof parent === "object" && keyToUpdate in parent) {
4826
+ if (parent[keyToUpdate] === oldValue) {
4827
+ parent[keyToUpdate] = newValue;
4828
+ updated = true;
4829
+ }
4830
+ }
4831
+ });
4832
+ }
4833
+ return { object: obj, updated };
4834
+ };
4545
4835
  var Resolver = class {
4546
4836
  constructor(init) {
4547
4837
  this.init = init;
@@ -4549,6 +4839,7 @@ var Resolver = class {
4549
4839
  const collection = this.tinaSchema.getCollection(collectionName);
4550
4840
  const extraFields = {};
4551
4841
  return {
4842
+ // return the collection and hasDocuments to resolve documents at a lower level
4552
4843
  documents: { collection, hasDocuments },
4553
4844
  ...collection,
4554
4845
  ...extraFields
@@ -4556,7 +4847,9 @@ var Resolver = class {
4556
4847
  };
4557
4848
  this.getRaw = async (fullPath) => {
4558
4849
  if (typeof fullPath !== "string") {
4559
- throw new Error(`fullPath must be of type string for getDocument request`);
4850
+ throw new Error(
4851
+ `fullPath must be of type string for getDocument request`
4852
+ );
4560
4853
  }
4561
4854
  return this.database.get(fullPath);
4562
4855
  };
@@ -4583,22 +4876,28 @@ var Resolver = class {
4583
4876
  );
4584
4877
  }
4585
4878
  };
4586
- this.getDocument = async (fullPath) => {
4879
+ this.getDocument = async (fullPath, opts = {}) => {
4587
4880
  if (typeof fullPath !== "string") {
4588
- throw new Error(`fullPath must be of type string for getDocument request`);
4881
+ throw new Error(
4882
+ `fullPath must be of type string for getDocument request`
4883
+ );
4589
4884
  }
4590
4885
  const rawData = await this.getRaw(fullPath);
4886
+ const hasReferences = opts?.checkReferences ? await this.hasReferences(fullPath, opts.collection) : void 0;
4591
4887
  return transformDocumentIntoPayload(
4592
4888
  fullPath,
4593
4889
  rawData,
4594
4890
  this.tinaSchema,
4595
4891
  this.config,
4596
- this.isAudit
4892
+ this.isAudit,
4893
+ hasReferences
4597
4894
  );
4598
4895
  };
4599
4896
  this.deleteDocument = async (fullPath) => {
4600
4897
  if (typeof fullPath !== "string") {
4601
- throw new Error(`fullPath must be of type string for getDocument request`);
4898
+ throw new Error(
4899
+ `fullPath must be of type string for getDocument request`
4900
+ );
4602
4901
  }
4603
4902
  await this.database.delete(fullPath);
4604
4903
  };
@@ -4633,7 +4932,9 @@ var Resolver = class {
4633
4932
  );
4634
4933
  } else {
4635
4934
  return this.buildFieldMutations(
4935
+ // @ts-ignore FIXME Argument of type 'string | object' is not assignable to parameter of type '{ [fieldName: string]: string | object | (string | object)[]; }'
4636
4936
  fieldValue,
4937
+ //@ts-ignore
4637
4938
  objectTemplate,
4638
4939
  existingData
4639
4940
  );
@@ -4645,6 +4946,7 @@ var Resolver = class {
4645
4946
  fieldValue.map(async (item) => {
4646
4947
  if (typeof item === "string") {
4647
4948
  throw new Error(
4949
+ //@ts-ignore
4648
4950
  `Expected object for template value for field ${field.name}`
4649
4951
  );
4650
4952
  }
@@ -4653,16 +4955,19 @@ var Resolver = class {
4653
4955
  });
4654
4956
  const [templateName] = Object.entries(item)[0];
4655
4957
  const template = templates.find(
4958
+ //@ts-ignore
4656
4959
  (template2) => template2.name === templateName
4657
4960
  );
4658
4961
  if (!template) {
4659
4962
  throw new Error(`Expected to find template ${templateName}`);
4660
4963
  }
4661
4964
  return {
4965
+ // @ts-ignore FIXME Argument of type 'unknown' is not assignable to parameter of type '{ [fieldName: string]: string | { [key: string]: unknown; } | (string | { [key: string]: unknown; })[]; }'
4662
4966
  ...await this.buildFieldMutations(
4663
4967
  item[template.name],
4664
4968
  template
4665
4969
  ),
4970
+ //@ts-ignore
4666
4971
  _template: template.name
4667
4972
  };
4668
4973
  })
@@ -4670,6 +4975,7 @@ var Resolver = class {
4670
4975
  } else {
4671
4976
  if (typeof fieldValue === "string") {
4672
4977
  throw new Error(
4978
+ //@ts-ignore
4673
4979
  `Expected object for template value for field ${field.name}`
4674
4980
  );
4675
4981
  }
@@ -4678,16 +4984,19 @@ var Resolver = class {
4678
4984
  });
4679
4985
  const [templateName] = Object.entries(fieldValue)[0];
4680
4986
  const template = templates.find(
4987
+ //@ts-ignore
4681
4988
  (template2) => template2.name === templateName
4682
4989
  );
4683
4990
  if (!template) {
4684
4991
  throw new Error(`Expected to find template ${templateName}`);
4685
4992
  }
4686
4993
  return {
4994
+ // @ts-ignore FIXME Argument of type 'unknown' is not assignable to parameter of type '{ [fieldName: string]: string | { [key: string]: unknown; } | (string | { [key: string]: unknown; })[]; }'
4687
4995
  ...await this.buildFieldMutations(
4688
4996
  fieldValue[template.name],
4689
4997
  template
4690
4998
  ),
4999
+ //@ts-ignore
4691
5000
  _template: template.name
4692
5001
  };
4693
5002
  }
@@ -4727,6 +5036,7 @@ var Resolver = class {
4727
5036
  return this.getDocument(realPath);
4728
5037
  }
4729
5038
  const params = await this.buildObjectMutations(
5039
+ // @ts-ignore
4730
5040
  args.params[collection.name],
4731
5041
  collection
4732
5042
  );
@@ -4772,6 +5082,7 @@ var Resolver = class {
4772
5082
  const values = {
4773
5083
  ...oldDoc,
4774
5084
  ...await this.buildFieldMutations(
5085
+ // @ts-ignore FIXME: failing on unknown, which we don't need to know because it's recursive
4775
5086
  templateParams,
4776
5087
  template,
4777
5088
  doc?._rawData
@@ -4785,13 +5096,22 @@ var Resolver = class {
4785
5096
  return this.getDocument(realPath);
4786
5097
  }
4787
5098
  const params = await this.buildObjectMutations(
5099
+ //@ts-ignore
4788
5100
  isCollectionSpecific ? args.params : args.params[collection.name],
4789
5101
  collection,
4790
5102
  doc?._rawData
4791
5103
  );
4792
- await this.database.put(realPath, { ...oldDoc, ...params }, collection.name);
5104
+ await this.database.put(
5105
+ realPath,
5106
+ { ...oldDoc, ...params },
5107
+ collection.name
5108
+ );
4793
5109
  return this.getDocument(realPath);
4794
5110
  };
5111
+ /**
5112
+ * Returns top-level fields which are not defined in the collection, so their
5113
+ * values are not eliminated from Tina when new values are saved
5114
+ */
4795
5115
  this.resolveLegacyValues = (oldDoc, collection) => {
4796
5116
  const legacyValues = {};
4797
5117
  Object.entries(oldDoc).forEach(([key, value]) => {
@@ -4896,6 +5216,40 @@ var Resolver = class {
4896
5216
  if (isDeletion) {
4897
5217
  const doc = await this.getDocument(realPath);
4898
5218
  await this.deleteDocument(realPath);
5219
+ if (await this.hasReferences(realPath, collection)) {
5220
+ const collRefs = await this.findReferences(realPath, collection);
5221
+ for (const [collection2, docsWithRefs] of Object.entries(collRefs)) {
5222
+ for (const [pathToDocWithRef, referencePaths] of Object.entries(
5223
+ docsWithRefs
5224
+ )) {
5225
+ let refDoc = await this.getRaw(pathToDocWithRef);
5226
+ let hasUpdate = false;
5227
+ for (const path7 of referencePaths) {
5228
+ const { object: object2, updated } = updateObjectWithJsonPath(
5229
+ refDoc,
5230
+ path7,
5231
+ realPath,
5232
+ null
5233
+ );
5234
+ refDoc = object2;
5235
+ hasUpdate = updated || hasUpdate;
5236
+ }
5237
+ if (hasUpdate) {
5238
+ const collectionWithRef = this.tinaSchema.getCollectionByFullPath(pathToDocWithRef);
5239
+ if (!collectionWithRef) {
5240
+ throw new Error(
5241
+ `Unable to find collection for ${pathToDocWithRef}`
5242
+ );
5243
+ }
5244
+ await this.database.put(
5245
+ pathToDocWithRef,
5246
+ refDoc,
5247
+ collectionWithRef.name
5248
+ );
5249
+ }
5250
+ }
5251
+ }
5252
+ }
4899
5253
  return doc;
4900
5254
  }
4901
5255
  if (isUpdateName) {
@@ -4912,12 +5266,49 @@ var Resolver = class {
4912
5266
  collection?.path,
4913
5267
  args.params.relativePath
4914
5268
  );
5269
+ if (newRealPath === realPath) {
5270
+ return doc;
5271
+ }
4915
5272
  await this.database.put(newRealPath, doc._rawData, collection.name);
4916
5273
  await this.deleteDocument(realPath);
5274
+ const collRefs = await this.findReferences(realPath, collection);
5275
+ for (const [collection2, docsWithRefs] of Object.entries(collRefs)) {
5276
+ for (const [pathToDocWithRef, referencePaths] of Object.entries(
5277
+ docsWithRefs
5278
+ )) {
5279
+ let docWithRef = await this.getRaw(pathToDocWithRef);
5280
+ let hasUpdate = false;
5281
+ for (const path7 of referencePaths) {
5282
+ const { object: object2, updated } = updateObjectWithJsonPath(
5283
+ docWithRef,
5284
+ path7,
5285
+ realPath,
5286
+ newRealPath
5287
+ );
5288
+ docWithRef = object2;
5289
+ hasUpdate = updated || hasUpdate;
5290
+ }
5291
+ if (hasUpdate) {
5292
+ const collectionWithRef = this.tinaSchema.getCollectionByFullPath(pathToDocWithRef);
5293
+ if (!collectionWithRef) {
5294
+ throw new Error(
5295
+ `Unable to find collection for ${pathToDocWithRef}`
5296
+ );
5297
+ }
5298
+ await this.database.put(
5299
+ pathToDocWithRef,
5300
+ docWithRef,
5301
+ collectionWithRef.name
5302
+ );
5303
+ }
5304
+ }
5305
+ }
4917
5306
  return this.getDocument(newRealPath);
4918
5307
  }
4919
5308
  if (alreadyExists === false) {
4920
- throw new Error(`Unable to update document, ${realPath} does not exist`);
5309
+ throw new Error(
5310
+ `Unable to update document, ${realPath} does not exist`
5311
+ );
4921
5312
  }
4922
5313
  return this.updateResolveDocument({
4923
5314
  collection,
@@ -4927,7 +5318,10 @@ var Resolver = class {
4927
5318
  isCollectionSpecific
4928
5319
  });
4929
5320
  } else {
4930
- return this.getDocument(realPath);
5321
+ return this.getDocument(realPath, {
5322
+ collection,
5323
+ checkReferences: true
5324
+ });
4931
5325
  }
4932
5326
  };
4933
5327
  this.resolveCollectionConnections = async ({ ids }) => {
@@ -4964,6 +5358,7 @@ var Resolver = class {
4964
5358
  },
4965
5359
  collection: referencedCollection,
4966
5360
  hydrator: (path7) => path7
5361
+ // just return the path
4967
5362
  }
4968
5363
  );
4969
5364
  const { edges } = resolvedCollectionConnection;
@@ -5031,6 +5426,82 @@ var Resolver = class {
5031
5426
  }
5032
5427
  };
5033
5428
  };
5429
+ /**
5430
+ * Checks if a document has references to it
5431
+ * @param id The id of the document to check for references
5432
+ * @param c The collection to check for references
5433
+ * @returns true if the document has references, false otherwise
5434
+ */
5435
+ this.hasReferences = async (id, c) => {
5436
+ let count = 0;
5437
+ await this.database.query(
5438
+ {
5439
+ collection: c.name,
5440
+ filterChain: makeFilterChain({
5441
+ conditions: [
5442
+ {
5443
+ filterPath: REFS_REFERENCE_FIELD,
5444
+ filterExpression: {
5445
+ _type: "string",
5446
+ _list: false,
5447
+ eq: id
5448
+ }
5449
+ }
5450
+ ]
5451
+ }),
5452
+ sort: REFS_COLLECTIONS_SORT_KEY
5453
+ },
5454
+ (refId) => {
5455
+ count++;
5456
+ return refId;
5457
+ }
5458
+ );
5459
+ if (count) {
5460
+ return true;
5461
+ }
5462
+ return false;
5463
+ };
5464
+ /**
5465
+ * Finds references to a document
5466
+ * @param id the id of the document to find references to
5467
+ * @param c the collection to find references in
5468
+ * @returns a map of references to the document
5469
+ */
5470
+ this.findReferences = async (id, c) => {
5471
+ const references = {};
5472
+ await this.database.query(
5473
+ {
5474
+ collection: c.name,
5475
+ filterChain: makeFilterChain({
5476
+ conditions: [
5477
+ {
5478
+ filterPath: REFS_REFERENCE_FIELD,
5479
+ filterExpression: {
5480
+ _type: "string",
5481
+ _list: false,
5482
+ eq: id
5483
+ }
5484
+ }
5485
+ ]
5486
+ }),
5487
+ sort: REFS_COLLECTIONS_SORT_KEY
5488
+ },
5489
+ (refId, rawItem) => {
5490
+ if (!references[c.name]) {
5491
+ references[c.name] = {};
5492
+ }
5493
+ if (!references[c.name][refId]) {
5494
+ references[c.name][refId] = [];
5495
+ }
5496
+ const referencePath = rawItem?.[REFS_PATH_FIELD];
5497
+ if (referencePath) {
5498
+ references[c.name][refId].push(referencePath);
5499
+ }
5500
+ return refId;
5501
+ }
5502
+ );
5503
+ return references;
5504
+ };
5034
5505
  this.buildFieldMutations = async (fieldParams, template, existingData) => {
5035
5506
  const accum = {};
5036
5507
  for (const passwordField of template.fields.filter(
@@ -5098,7 +5569,7 @@ var Resolver = class {
5098
5569
  }
5099
5570
  break;
5100
5571
  case "rich-text":
5101
- accum[fieldName] = stringifyMDX(
5572
+ accum[fieldName] = serializeMDX(
5102
5573
  fieldValue,
5103
5574
  field,
5104
5575
  (fieldValue2) => resolveMediaCloudToRelative(
@@ -5117,6 +5588,27 @@ var Resolver = class {
5117
5588
  }
5118
5589
  return accum;
5119
5590
  };
5591
+ /**
5592
+ * A mutation looks nearly identical between updateDocument:
5593
+ * ```graphql
5594
+ * updateDocument(collection: $collection,relativePath: $path, params: {
5595
+ * post: {
5596
+ * title: "Hello, World"
5597
+ * }
5598
+ * })`
5599
+ * ```
5600
+ * and `updatePostDocument`:
5601
+ * ```graphql
5602
+ * updatePostDocument(relativePath: $path, params: {
5603
+ * title: "Hello, World"
5604
+ * })
5605
+ * ```
5606
+ * The problem here is that we don't know whether the payload came from `updateDocument`
5607
+ * or `updatePostDocument` (we could, but for now it's easier not to pipe those details through),
5608
+ * But we do know that when given a `args.collection` value, we can assume that
5609
+ * this was a `updateDocument` request, and thus - should grab the data
5610
+ * from the corresponding field name in the key
5611
+ */
5120
5612
  this.buildParams = (args) => {
5121
5613
  try {
5122
5614
  assertShape(
@@ -5184,8 +5676,129 @@ var resolveDateInput = (value) => {
5184
5676
  return date;
5185
5677
  };
5186
5678
 
5187
- // src/resolve.ts
5188
- import set from "lodash.set";
5679
+ // src/resolver/auth-fields.ts
5680
+ import { set } from "es-toolkit/compat";
5681
+ async function getUserDocumentContext(tinaSchema, resolver) {
5682
+ const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
5683
+ if (!collection) {
5684
+ throw new Error("Auth collection not found");
5685
+ }
5686
+ const userFields = mapUserFields(collection, ["_rawData"]);
5687
+ if (!userFields.length) {
5688
+ throw new Error(`No user field found in collection ${collection.name}`);
5689
+ }
5690
+ if (userFields.length > 1) {
5691
+ throw new Error(
5692
+ `Multiple user fields found in collection ${collection.name}`
5693
+ );
5694
+ }
5695
+ const userField = userFields[0];
5696
+ const realPath = `${collection.path}/index.json`;
5697
+ const userDoc = await resolver.getDocument(realPath);
5698
+ const users = get(userDoc, userField.path);
5699
+ if (!users) {
5700
+ throw new Error("No users found");
5701
+ }
5702
+ return { collection, userField, users, userDoc, realPath };
5703
+ }
5704
+ function findUserInCollection(users, userField, userSub) {
5705
+ const { idFieldName } = userField;
5706
+ if (!idFieldName) {
5707
+ throw new Error("No uid field found on user field");
5708
+ }
5709
+ return users.find((u) => u[idFieldName] === userSub) || null;
5710
+ }
5711
+ async function handleAuthenticate({
5712
+ tinaSchema,
5713
+ resolver,
5714
+ sub,
5715
+ password,
5716
+ ctxUser
5717
+ }) {
5718
+ const userSub = sub || ctxUser?.sub;
5719
+ const { userField, users } = await getUserDocumentContext(
5720
+ tinaSchema,
5721
+ resolver
5722
+ );
5723
+ const user = findUserInCollection(users, userField, userSub);
5724
+ if (!user) {
5725
+ return null;
5726
+ }
5727
+ const { passwordFieldName } = userField;
5728
+ const saltedHash = get(user, [passwordFieldName || "", "value"]);
5729
+ if (!saltedHash) {
5730
+ throw new Error("No password field found on user field");
5731
+ }
5732
+ const matches = await checkPasswordHash({
5733
+ saltedHash,
5734
+ password
5735
+ });
5736
+ return matches ? user : null;
5737
+ }
5738
+ async function handleAuthorize({
5739
+ tinaSchema,
5740
+ resolver,
5741
+ sub,
5742
+ ctxUser
5743
+ }) {
5744
+ const userSub = sub || ctxUser?.sub;
5745
+ const { userField, users } = await getUserDocumentContext(
5746
+ tinaSchema,
5747
+ resolver
5748
+ );
5749
+ const user = findUserInCollection(users, userField, userSub);
5750
+ return user ? user : null;
5751
+ }
5752
+ async function handleUpdatePassword({
5753
+ tinaSchema,
5754
+ resolver,
5755
+ password,
5756
+ ctxUser
5757
+ }) {
5758
+ if (!ctxUser?.sub) {
5759
+ throw new Error("Not authorized");
5760
+ }
5761
+ if (!password) {
5762
+ throw new Error("No password provided");
5763
+ }
5764
+ const { collection, userField, users, realPath } = await getUserDocumentContext(tinaSchema, resolver);
5765
+ const { idFieldName, passwordFieldName } = userField;
5766
+ const user = users.find((u) => u[idFieldName] === ctxUser.sub);
5767
+ if (!user) {
5768
+ throw new Error("Not authorized");
5769
+ }
5770
+ user[passwordFieldName] = {
5771
+ value: password,
5772
+ passwordChangeRequired: false
5773
+ };
5774
+ const params = {};
5775
+ set(
5776
+ params,
5777
+ userField.path.slice(1),
5778
+ // remove _rawData from users path
5779
+ users.map((u) => {
5780
+ if (user[idFieldName] === u[idFieldName]) {
5781
+ return user;
5782
+ }
5783
+ return {
5784
+ // don't overwrite other users' passwords
5785
+ ...u,
5786
+ [passwordFieldName]: {
5787
+ ...u[passwordFieldName],
5788
+ value: ""
5789
+ }
5790
+ };
5791
+ })
5792
+ );
5793
+ await resolver.updateResolveDocument({
5794
+ collection,
5795
+ args: { params },
5796
+ realPath,
5797
+ isCollectionSpecific: true,
5798
+ isAddPendingDocument: false
5799
+ });
5800
+ return true;
5801
+ }
5189
5802
 
5190
5803
  // src/error.ts
5191
5804
  import { GraphQLError as GraphQLError3 } from "graphql";
@@ -5216,7 +5829,10 @@ var resolve = async ({
5216
5829
  const graphQLSchema = buildASTSchema(graphQLSchemaAst);
5217
5830
  const tinaConfig = await database.getTinaSchema();
5218
5831
  const tinaSchema = await createSchema({
5832
+ // TODO: please update all the types to import from @tinacms/schema-tools
5833
+ // @ts-ignore
5219
5834
  schema: tinaConfig,
5835
+ // @ts-ignore
5220
5836
  flags: tinaConfig?.meta?.flags
5221
5837
  });
5222
5838
  const resolver = createResolver({
@@ -5233,8 +5849,7 @@ var resolve = async ({
5233
5849
  database
5234
5850
  },
5235
5851
  typeResolver: async (source, _args, info) => {
5236
- if (source.__typename)
5237
- return source.__typename;
5852
+ if (source.__typename) return source.__typename;
5238
5853
  const namedType = getNamedType(info.returnType).toString();
5239
5854
  const lookup = await database.getLookup(namedType);
5240
5855
  if (lookup.resolveType === "unionData") {
@@ -5293,123 +5908,42 @@ var resolve = async ({
5293
5908
  );
5294
5909
  }
5295
5910
  }
5296
- if (info.fieldName === "authenticate" || info.fieldName === "authorize") {
5297
- const sub = args.sub || ctxUser?.sub;
5298
- const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
5299
- if (!collection) {
5300
- throw new Error("Auth collection not found");
5301
- }
5302
- const userFields = mapUserFields(collection, ["_rawData"]);
5303
- if (!userFields.length) {
5304
- throw new Error(
5305
- `No user field found in collection ${collection.name}`
5306
- );
5307
- }
5308
- if (userFields.length > 1) {
5309
- throw new Error(
5310
- `Multiple user fields found in collection ${collection.name}`
5311
- );
5312
- }
5313
- const userField = userFields[0];
5314
- const realPath = `${collection.path}/index.json`;
5315
- const userDoc = await resolver.getDocument(realPath);
5316
- const users = get(userDoc, userField.path);
5317
- if (!users) {
5318
- throw new Error("No users found");
5319
- }
5320
- const { idFieldName, passwordFieldName } = userField;
5321
- if (!idFieldName) {
5322
- throw new Error("No uid field found on user field");
5323
- }
5324
- const user = users.find((u) => u[idFieldName] === sub);
5325
- if (!user) {
5326
- return null;
5327
- }
5328
- if (info.fieldName === "authenticate") {
5329
- const saltedHash = get(user, [passwordFieldName || "", "value"]);
5330
- if (!saltedHash) {
5331
- throw new Error("No password field found on user field");
5332
- }
5333
- const matches = await checkPasswordHash({
5334
- saltedHash,
5335
- password: args.password
5336
- });
5337
- if (matches) {
5338
- return user;
5339
- }
5340
- return null;
5341
- }
5342
- return user;
5911
+ if (info.fieldName === "authenticate") {
5912
+ return handleAuthenticate({
5913
+ tinaSchema,
5914
+ resolver,
5915
+ sub: args.sub,
5916
+ password: args.password,
5917
+ info,
5918
+ ctxUser
5919
+ });
5920
+ }
5921
+ if (info.fieldName === "authorize") {
5922
+ return handleAuthorize({
5923
+ tinaSchema,
5924
+ resolver,
5925
+ sub: args.sub,
5926
+ info,
5927
+ ctxUser
5928
+ });
5343
5929
  }
5344
5930
  if (info.fieldName === "updatePassword") {
5345
- if (!ctxUser?.sub) {
5346
- throw new Error("Not authorized");
5347
- }
5348
- if (!args.password) {
5349
- throw new Error("No password provided");
5350
- }
5351
- const collection = tinaSchema.getCollections().find((c) => c.isAuthCollection);
5352
- if (!collection) {
5353
- throw new Error("Auth collection not found");
5354
- }
5355
- const userFields = mapUserFields(collection, ["_rawData"]);
5356
- if (!userFields.length) {
5357
- throw new Error(
5358
- `No user field found in collection ${collection.name}`
5359
- );
5360
- }
5361
- if (userFields.length > 1) {
5362
- throw new Error(
5363
- `Multiple user fields found in collection ${collection.name}`
5364
- );
5365
- }
5366
- const userField = userFields[0];
5367
- const realPath = `${collection.path}/index.json`;
5368
- const userDoc = await resolver.getDocument(realPath);
5369
- const users = get(userDoc, userField.path);
5370
- if (!users) {
5371
- throw new Error("No users found");
5372
- }
5373
- const { idFieldName, passwordFieldName } = userField;
5374
- const user = users.find((u) => u[idFieldName] === ctxUser.sub);
5375
- if (!user) {
5376
- throw new Error("Not authorized");
5377
- }
5378
- user[passwordFieldName] = {
5379
- value: args.password,
5380
- passwordChangeRequired: false
5381
- };
5382
- const params = {};
5383
- set(
5384
- params,
5385
- userField.path.slice(1),
5386
- users.map((u) => {
5387
- if (user[idFieldName] === u[idFieldName]) {
5388
- return user;
5389
- }
5390
- return {
5391
- ...u,
5392
- [passwordFieldName]: {
5393
- ...u[passwordFieldName],
5394
- value: ""
5395
- }
5396
- };
5397
- })
5398
- );
5399
- await resolver.updateResolveDocument({
5400
- collection,
5401
- args: { params },
5402
- realPath,
5403
- isCollectionSpecific: true,
5404
- isAddPendingDocument: false
5931
+ return handleUpdatePassword({
5932
+ tinaSchema,
5933
+ resolver,
5934
+ password: args.password,
5935
+ info,
5936
+ ctxUser
5405
5937
  });
5406
- return true;
5407
5938
  }
5408
5939
  if (!lookup) {
5409
5940
  return value;
5410
5941
  }
5411
5942
  const isCreation = lookup[info.fieldName] === "create";
5412
5943
  switch (lookup.resolveType) {
5944
+ /**
5945
+ * `node(id: $id)`
5946
+ */
5413
5947
  case "nodeDocument":
5414
5948
  assertShape(
5415
5949
  args,
@@ -5441,6 +5975,7 @@ var resolve = async ({
5441
5975
  collection: args.collection,
5442
5976
  isMutation,
5443
5977
  isCreation,
5978
+ // Right now this is the only case for deletion
5444
5979
  isDeletion: info.fieldName === "deleteDocument",
5445
5980
  isFolderCreation: info.fieldName === "createFolder",
5446
5981
  isUpdateName: Boolean(args?.params?.relativePath),
@@ -5450,6 +5985,9 @@ var resolve = async ({
5450
5985
  return result;
5451
5986
  }
5452
5987
  return value;
5988
+ /**
5989
+ * eg `getMovieDocument.data.actors`
5990
+ */
5453
5991
  case "multiCollectionDocumentList":
5454
5992
  if (Array.isArray(value)) {
5455
5993
  return {
@@ -5461,7 +5999,15 @@ var resolve = async ({
5461
5999
  }
5462
6000
  if (info.fieldName === "documents" && value?.collection && value?.hasDocuments) {
5463
6001
  let filter = args.filter;
5464
- if (typeof args?.filter !== "undefined" && args?.filter !== null && typeof value?.collection?.name === "string" && Object.keys(args.filter).includes(value?.collection?.name) && typeof args.filter[value?.collection?.name] !== "undefined") {
6002
+ if (
6003
+ // 1. Make sure that the filter exists
6004
+ typeof args?.filter !== "undefined" && args?.filter !== null && // 2. Make sure that the collection name exists
6005
+ // @ts-ignore
6006
+ typeof value?.collection?.name === "string" && // 3. Make sure that the collection name is in the filter and is not undefined
6007
+ // @ts-ignore
6008
+ Object.keys(args.filter).includes(value?.collection?.name) && // @ts-ignore
6009
+ typeof args.filter[value?.collection?.name] !== "undefined"
6010
+ ) {
5465
6011
  filter = args.filter[value.collection.name];
5466
6012
  }
5467
6013
  return resolver.resolveCollectionConnection({
@@ -5469,12 +6015,20 @@ var resolve = async ({
5469
6015
  ...args,
5470
6016
  filter
5471
6017
  },
6018
+ // @ts-ignore
5472
6019
  collection: value.collection
5473
6020
  });
5474
6021
  }
5475
6022
  throw new Error(
5476
6023
  `Expected an array for result of ${info.fieldName} at ${info.path}`
5477
6024
  );
6025
+ /**
6026
+ * Collections-specific getter
6027
+ * eg. `getPostDocument`/`createPostDocument`/`updatePostDocument`
6028
+ *
6029
+ * if coming from a query result
6030
+ * the field will be `node`
6031
+ */
5478
6032
  case "collectionDocument": {
5479
6033
  if (value) {
5480
6034
  return value;
@@ -5489,11 +6043,32 @@ var resolve = async ({
5489
6043
  });
5490
6044
  return result;
5491
6045
  }
6046
+ /**
6047
+ * Collections-specific list getter
6048
+ * eg. `getPageList`
6049
+ */
5492
6050
  case "collectionDocumentList":
5493
6051
  return resolver.resolveCollectionConnection({
5494
6052
  args,
5495
6053
  collection: tinaSchema.getCollection(lookup.collection)
5496
6054
  });
6055
+ /**
6056
+ * A polymorphic data set, it can be from a document's data
6057
+ * of any nested object which can be one of many shapes
6058
+ *
6059
+ * ```graphql
6060
+ * getPostDocument(relativePath: $relativePath) {
6061
+ * data {...} <- this part
6062
+ * }
6063
+ * ```
6064
+ * ```graphql
6065
+ * getBlockDocument(relativePath: $relativePath) {
6066
+ * data {
6067
+ * blocks {...} <- or this part
6068
+ * }
6069
+ * }
6070
+ * ```
6071
+ */
5497
6072
  case "unionData":
5498
6073
  if (!value) {
5499
6074
  if (args.relativePath) {
@@ -5558,8 +6133,7 @@ var TinaLevelClient = class extends ManyLevelGuest {
5558
6133
  this.port = port || 9e3;
5559
6134
  }
5560
6135
  openConnection() {
5561
- if (this._connected)
5562
- return;
6136
+ if (this._connected) return;
5563
6137
  const socket = connect(this.port);
5564
6138
  pipeline(socket, this.createRpcStream(), socket, () => {
5565
6139
  this._connected = false;
@@ -5569,11 +6143,11 @@ var TinaLevelClient = class extends ManyLevelGuest {
5569
6143
  };
5570
6144
 
5571
6145
  // src/database/index.ts
5572
- import path4 from "path";
6146
+ import path4 from "node:path";
5573
6147
  import { GraphQLError as GraphQLError5 } from "graphql";
5574
6148
  import micromatch2 from "micromatch";
5575
6149
  import sha2 from "js-sha1";
5576
- import set2 from "lodash.set";
6150
+ import { set as set2 } from "es-toolkit/compat";
5577
6151
  var createLocalDatabase = (config) => {
5578
6152
  const level = new TinaLevelClient(config?.port);
5579
6153
  level.openConnection();
@@ -5704,6 +6278,7 @@ var Database = class {
5704
6278
  );
5705
6279
  const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
5706
6280
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
6281
+ const collectionReferences = (await this.getCollectionReferences())?.[collection.name];
5707
6282
  const normalizedPath = normalizePath(filepath);
5708
6283
  if (!collection?.isDetached) {
5709
6284
  if (this.bridge) {
@@ -5732,6 +6307,14 @@ var Database = class {
5732
6307
  let delOps = [];
5733
6308
  if (!isGitKeep(normalizedPath, collection)) {
5734
6309
  putOps = [
6310
+ ...makeRefOpsForDocument(
6311
+ normalizedPath,
6312
+ collection?.name,
6313
+ collectionReferences,
6314
+ dataFields,
6315
+ "put",
6316
+ level
6317
+ ),
5735
6318
  ...makeIndexOpsForDocument(
5736
6319
  normalizedPath,
5737
6320
  collection?.name,
@@ -5740,6 +6323,7 @@ var Database = class {
5740
6323
  "put",
5741
6324
  level
5742
6325
  ),
6326
+ // folder indices
5743
6327
  ...makeIndexOpsForDocument(
5744
6328
  normalizedPath,
5745
6329
  `${collection?.name}_${folderKey}`,
@@ -5754,6 +6338,14 @@ var Database = class {
5754
6338
  SUBLEVEL_OPTIONS
5755
6339
  ).get(normalizedPath);
5756
6340
  delOps = existingItem ? [
6341
+ ...makeRefOpsForDocument(
6342
+ normalizedPath,
6343
+ collection?.name,
6344
+ collectionReferences,
6345
+ existingItem,
6346
+ "del",
6347
+ level
6348
+ ),
5757
6349
  ...makeIndexOpsForDocument(
5758
6350
  normalizedPath,
5759
6351
  collection?.name,
@@ -5762,6 +6354,7 @@ var Database = class {
5762
6354
  "del",
5763
6355
  level
5764
6356
  ),
6357
+ // folder indices
5765
6358
  ...makeIndexOpsForDocument(
5766
6359
  normalizedPath,
5767
6360
  `${collection?.name}_${folderKey}`,
@@ -5800,6 +6393,7 @@ var Database = class {
5800
6393
  );
5801
6394
  collectionIndexDefinitions = indexDefinitions?.[collectionName];
5802
6395
  }
6396
+ const collectionReferences = (await this.getCollectionReferences())?.[collectionName];
5803
6397
  const normalizedPath = normalizePath(filepath);
5804
6398
  const dataFields = await this.formatBodyOnPayload(filepath, data);
5805
6399
  const collection = await this.collectionForPath(filepath);
@@ -5847,6 +6441,14 @@ var Database = class {
5847
6441
  let delOps = [];
5848
6442
  if (!isGitKeep(normalizedPath, collection)) {
5849
6443
  putOps = [
6444
+ ...makeRefOpsForDocument(
6445
+ normalizedPath,
6446
+ collectionName,
6447
+ collectionReferences,
6448
+ dataFields,
6449
+ "put",
6450
+ level
6451
+ ),
5850
6452
  ...makeIndexOpsForDocument(
5851
6453
  normalizedPath,
5852
6454
  collectionName,
@@ -5855,6 +6457,7 @@ var Database = class {
5855
6457
  "put",
5856
6458
  level
5857
6459
  ),
6460
+ // folder indices
5858
6461
  ...makeIndexOpsForDocument(
5859
6462
  normalizedPath,
5860
6463
  `${collection?.name}_${folderKey}`,
@@ -5869,6 +6472,14 @@ var Database = class {
5869
6472
  SUBLEVEL_OPTIONS
5870
6473
  ).get(normalizedPath);
5871
6474
  delOps = existingItem ? [
6475
+ ...makeRefOpsForDocument(
6476
+ normalizedPath,
6477
+ collectionName,
6478
+ collectionReferences,
6479
+ existingItem,
6480
+ "del",
6481
+ level
6482
+ ),
5872
6483
  ...makeIndexOpsForDocument(
5873
6484
  normalizedPath,
5874
6485
  collectionName,
@@ -5877,6 +6488,7 @@ var Database = class {
5877
6488
  "del",
5878
6489
  level
5879
6490
  ),
6491
+ // folder indices
5880
6492
  ...makeIndexOpsForDocument(
5881
6493
  normalizedPath,
5882
6494
  `${collection?.name}_${folderKey}`,
@@ -5910,8 +6522,7 @@ var Database = class {
5910
6522
  throw new TinaFetchError(`Error in PUT for ${filepath}`, {
5911
6523
  originalError: error,
5912
6524
  file: filepath,
5913
- collection: collectionName,
5914
- stack: error.stack
6525
+ collection: collectionName
5915
6526
  });
5916
6527
  }
5917
6528
  };
@@ -5954,6 +6565,7 @@ var Database = class {
5954
6565
  aliasedData,
5955
6566
  extension,
5956
6567
  writeTemplateKey,
6568
+ //templateInfo.type === 'union',
5957
6569
  {
5958
6570
  frontmatterFormat: collection?.frontmatterFormat,
5959
6571
  frontmatterDelimiters: collection?.frontmatterDelimiters
@@ -5992,6 +6604,7 @@ var Database = class {
5992
6604
  SUBLEVEL_OPTIONS
5993
6605
  ).get(graphqlPath);
5994
6606
  };
6607
+ //TODO - is there a reason why the database fetches some config with "bridge.get", and some with "store.get"?
5995
6608
  this.getGraphQLSchemaFromBridge = async () => {
5996
6609
  if (!this.bridge) {
5997
6610
  throw new Error(`No bridge configured`);
@@ -6028,6 +6641,22 @@ var Database = class {
6028
6641
  this.tinaSchema = await createSchema({ schema });
6029
6642
  return this.tinaSchema;
6030
6643
  };
6644
+ this.getCollectionReferences = async (level) => {
6645
+ if (this.collectionReferences) {
6646
+ return this.collectionReferences;
6647
+ }
6648
+ const result = {};
6649
+ const schema = await this.getSchema(level || this.contentLevel);
6650
+ const collections = schema.getCollections();
6651
+ for (const collection of collections) {
6652
+ const collectionReferences = this.tinaSchema.findReferencesFromCollection(
6653
+ collection.name
6654
+ );
6655
+ result[collection.name] = collectionReferences;
6656
+ }
6657
+ this.collectionReferences = result;
6658
+ return result;
6659
+ };
6031
6660
  this.getIndexDefinitions = async (level) => {
6032
6661
  if (!this.collectionIndexDefinitions) {
6033
6662
  await new Promise(async (resolve2, reject) => {
@@ -6037,10 +6666,53 @@ var Database = class {
6037
6666
  const collections = schema.getCollections();
6038
6667
  for (const collection of collections) {
6039
6668
  const indexDefinitions = {
6040
- [DEFAULT_COLLECTION_SORT_KEY]: { fields: [] }
6669
+ [DEFAULT_COLLECTION_SORT_KEY]: { fields: [] },
6670
+ // provide a default sort key which is the file sort
6671
+ // pseudo-index for the collection's references
6672
+ [REFS_COLLECTIONS_SORT_KEY]: {
6673
+ fields: [
6674
+ {
6675
+ name: REFS_REFERENCE_FIELD,
6676
+ type: "string",
6677
+ list: false
6678
+ },
6679
+ {
6680
+ name: REFS_PATH_FIELD,
6681
+ type: "string",
6682
+ list: false
6683
+ }
6684
+ ]
6685
+ }
6041
6686
  };
6042
- if (collection.fields) {
6043
- for (const field of collection.fields) {
6687
+ let fields = [];
6688
+ if (collection.templates) {
6689
+ const templateFieldMap = {};
6690
+ const conflictedFields = /* @__PURE__ */ new Set();
6691
+ for (const template of collection.templates) {
6692
+ for (const field of template.fields) {
6693
+ if (!templateFieldMap[field.name]) {
6694
+ templateFieldMap[field.name] = field;
6695
+ } else {
6696
+ if (templateFieldMap[field.name].type !== field.type) {
6697
+ console.warn(
6698
+ `Field ${field.name} has conflicting types in templates - skipping index`
6699
+ );
6700
+ conflictedFields.add(field.name);
6701
+ }
6702
+ }
6703
+ }
6704
+ }
6705
+ for (const conflictedField in conflictedFields) {
6706
+ delete templateFieldMap[conflictedField];
6707
+ }
6708
+ for (const field of Object.values(templateFieldMap)) {
6709
+ fields.push(field);
6710
+ }
6711
+ } else if (collection.fields) {
6712
+ fields = collection.fields;
6713
+ }
6714
+ if (fields) {
6715
+ for (const field of fields) {
6044
6716
  if (field.indexed !== void 0 && field.indexed === false || field.type === "object") {
6045
6717
  continue;
6046
6718
  }
@@ -6188,29 +6860,35 @@ var Database = class {
6188
6860
  }
6189
6861
  startKey = startKey || key || "";
6190
6862
  endKey = key || "";
6191
- edges = [...edges, { cursor: key, path: filepath }];
6863
+ edges = [...edges, { cursor: key, path: filepath, value: itemRecord }];
6192
6864
  }
6193
6865
  return {
6194
- edges: await sequential(edges, async (edge) => {
6195
- try {
6196
- const node = await hydrator(edge.path);
6197
- return {
6198
- node,
6199
- cursor: btoa(edge.cursor)
6200
- };
6201
- } catch (error) {
6202
- console.log(error);
6203
- if (error instanceof Error && (!edge.path.includes(".tina/__generated__/_graphql.json") || !edge.path.includes("tina/__generated__/_graphql.json"))) {
6204
- throw new TinaQueryError({
6205
- originalError: error,
6206
- file: edge.path,
6207
- collection: collection.name,
6208
- stack: error.stack
6209
- });
6866
+ edges: await sequential(
6867
+ edges,
6868
+ async ({
6869
+ cursor,
6870
+ path: path7,
6871
+ value
6872
+ }) => {
6873
+ try {
6874
+ const node = await hydrator(path7, value);
6875
+ return {
6876
+ node,
6877
+ cursor: btoa(cursor)
6878
+ };
6879
+ } catch (error) {
6880
+ console.log(error);
6881
+ if (error instanceof Error && (!path7.includes(".tina/__generated__/_graphql.json") || !path7.includes("tina/__generated__/_graphql.json"))) {
6882
+ throw new TinaQueryError({
6883
+ originalError: error,
6884
+ file: path7,
6885
+ collection: collection.name
6886
+ });
6887
+ }
6888
+ throw error;
6210
6889
  }
6211
- throw error;
6212
6890
  }
6213
- }),
6891
+ ),
6214
6892
  pageInfo: {
6215
6893
  hasPreviousPage,
6216
6894
  hasNextPage,
@@ -6334,13 +7012,14 @@ var Database = class {
6334
7012
  documentPaths,
6335
7013
  async (collection, documentPaths2) => {
6336
7014
  if (collection && !collection.isDetached) {
6337
- await _indexContent(
6338
- this,
6339
- this.contentLevel,
6340
- documentPaths2,
7015
+ await _indexContent({
7016
+ database: this,
7017
+ level: this.contentLevel,
7018
+ documentPaths: documentPaths2,
6341
7019
  enqueueOps,
6342
- collection
6343
- );
7020
+ collection,
7021
+ isPartialReindex: true
7022
+ });
6344
7023
  }
6345
7024
  }
6346
7025
  );
@@ -6356,17 +7035,18 @@ var Database = class {
6356
7035
  throw new Error(`No collection found for path: ${filepath}`);
6357
7036
  }
6358
7037
  const indexDefinitions = await this.getIndexDefinitions(this.contentLevel);
7038
+ const collectionReferences = (await this.getCollectionReferences())?.[collection.name];
6359
7039
  const collectionIndexDefinitions = indexDefinitions?.[collection.name];
6360
7040
  let level = this.contentLevel;
6361
7041
  if (collection?.isDetached) {
6362
7042
  level = this.appLevel.sublevel(collection?.name, SUBLEVEL_OPTIONS);
6363
7043
  }
6364
- const itemKey = normalizePath(filepath);
7044
+ const normalizedPath = normalizePath(filepath);
6365
7045
  const rootSublevel = level.sublevel(
6366
7046
  CONTENT_ROOT_PREFIX,
6367
7047
  SUBLEVEL_OPTIONS
6368
7048
  );
6369
- const item = await rootSublevel.get(itemKey);
7049
+ const item = await rootSublevel.get(normalizedPath);
6370
7050
  if (item) {
6371
7051
  const folderTreeBuilder = new FolderTreeBuilder();
6372
7052
  const folderKey = folderTreeBuilder.update(
@@ -6374,16 +7054,25 @@ var Database = class {
6374
7054
  collection.path || ""
6375
7055
  );
6376
7056
  await this.contentLevel.batch([
7057
+ ...makeRefOpsForDocument(
7058
+ normalizedPath,
7059
+ collection.name,
7060
+ collectionReferences,
7061
+ item,
7062
+ "del",
7063
+ level
7064
+ ),
6377
7065
  ...makeIndexOpsForDocument(
6378
- filepath,
7066
+ normalizedPath,
6379
7067
  collection.name,
6380
7068
  collectionIndexDefinitions,
6381
7069
  item,
6382
7070
  "del",
6383
7071
  level
6384
7072
  ),
7073
+ // folder indices
6385
7074
  ...makeIndexOpsForDocument(
6386
- filepath,
7075
+ normalizedPath,
6387
7076
  `${collection.name}_${folderKey}`,
6388
7077
  collectionIndexDefinitions,
6389
7078
  item,
@@ -6392,17 +7081,17 @@ var Database = class {
6392
7081
  ),
6393
7082
  {
6394
7083
  type: "del",
6395
- key: itemKey,
7084
+ key: normalizedPath,
6396
7085
  sublevel: rootSublevel
6397
7086
  }
6398
7087
  ]);
6399
7088
  }
6400
7089
  if (!collection?.isDetached) {
6401
7090
  if (this.bridge) {
6402
- await this.bridge.delete(normalizePath(filepath));
7091
+ await this.bridge.delete(normalizedPath);
6403
7092
  }
6404
7093
  try {
6405
- await this.onDelete(normalizePath(filepath));
7094
+ await this.onDelete(normalizedPath);
6406
7095
  } catch (e) {
6407
7096
  throw new GraphQLError5(
6408
7097
  `Error running onDelete hook for ${filepath}: ${e}`,
@@ -6437,20 +7126,26 @@ var Database = class {
6437
7126
  );
6438
7127
  const doc = await level2.keys({ limit: 1 }).next();
6439
7128
  if (!doc) {
6440
- await _indexContent(
6441
- this,
6442
- level2,
6443
- contentPaths,
7129
+ await _indexContent({
7130
+ database: this,
7131
+ level: level2,
7132
+ documentPaths: contentPaths,
6444
7133
  enqueueOps,
6445
7134
  collection,
6446
- userFields.map((field) => [
7135
+ passwordFields: userFields.map((field) => [
6447
7136
  ...field.path,
6448
7137
  field.passwordFieldName
6449
7138
  ])
6450
- );
7139
+ });
6451
7140
  }
6452
7141
  } else {
6453
- await _indexContent(this, level, contentPaths, enqueueOps, collection);
7142
+ await _indexContent({
7143
+ database: this,
7144
+ level,
7145
+ documentPaths: contentPaths,
7146
+ enqueueOps,
7147
+ collection
7148
+ });
6454
7149
  }
6455
7150
  }
6456
7151
  );
@@ -6536,6 +7231,9 @@ var Database = class {
6536
7231
  info: templateInfo
6537
7232
  };
6538
7233
  }
7234
+ /**
7235
+ * Clears the internal cache of the tinaSchema and the lookup file. This allows the state to be reset
7236
+ */
6539
7237
  clearCache() {
6540
7238
  this.tinaSchema = null;
6541
7239
  this._lookup = null;
@@ -6586,7 +7284,15 @@ var hashPasswordValues = async (data, passwordFields) => Promise.all(
6586
7284
  )
6587
7285
  );
6588
7286
  var isGitKeep = (filepath, collection) => filepath.endsWith(`.gitkeep.${collection?.format || "md"}`);
6589
- var _indexContent = async (database, level, documentPaths, enqueueOps, collection, passwordFields) => {
7287
+ var _indexContent = async ({
7288
+ database,
7289
+ level,
7290
+ documentPaths,
7291
+ enqueueOps,
7292
+ collection,
7293
+ passwordFields,
7294
+ isPartialReindex
7295
+ }) => {
6590
7296
  let collectionIndexDefinitions;
6591
7297
  let collectionPath;
6592
7298
  if (collection) {
@@ -6597,6 +7303,7 @@ var _indexContent = async (database, level, documentPaths, enqueueOps, collectio
6597
7303
  }
6598
7304
  collectionPath = collection.path;
6599
7305
  }
7306
+ const collectionReferences = (await database.getCollectionReferences())?.[collection?.name];
6600
7307
  const tinaSchema = await database.getSchema();
6601
7308
  let templateInfo = null;
6602
7309
  if (collection) {
@@ -6618,12 +7325,61 @@ var _indexContent = async (database, level, documentPaths, enqueueOps, collectio
6618
7325
  await hashPasswordValues(aliasedData, passwordFields);
6619
7326
  }
6620
7327
  const normalizedPath = normalizePath(filepath);
7328
+ const rootSublevel = level.sublevel(
7329
+ CONTENT_ROOT_PREFIX,
7330
+ SUBLEVEL_OPTIONS
7331
+ );
6621
7332
  const folderKey = folderTreeBuilder.update(
6622
7333
  normalizedPath,
6623
7334
  collectionPath || ""
6624
7335
  );
7336
+ if (isPartialReindex) {
7337
+ const item = await rootSublevel.get(normalizedPath);
7338
+ if (item) {
7339
+ await database.contentLevel.batch([
7340
+ ...makeRefOpsForDocument(
7341
+ normalizedPath,
7342
+ collection?.name,
7343
+ collectionReferences,
7344
+ item,
7345
+ "del",
7346
+ level
7347
+ ),
7348
+ ...makeIndexOpsForDocument(
7349
+ normalizedPath,
7350
+ collection.name,
7351
+ collectionIndexDefinitions,
7352
+ item,
7353
+ "del",
7354
+ level
7355
+ ),
7356
+ // folder indices
7357
+ ...makeIndexOpsForDocument(
7358
+ normalizedPath,
7359
+ `${collection.name}_${folderKey}`,
7360
+ collectionIndexDefinitions,
7361
+ item,
7362
+ "del",
7363
+ level
7364
+ ),
7365
+ {
7366
+ type: "del",
7367
+ key: normalizedPath,
7368
+ sublevel: rootSublevel
7369
+ }
7370
+ ]);
7371
+ }
7372
+ }
6625
7373
  if (!isGitKeep(filepath, collection)) {
6626
7374
  await enqueueOps([
7375
+ ...makeRefOpsForDocument(
7376
+ normalizedPath,
7377
+ collection?.name,
7378
+ collectionReferences,
7379
+ aliasedData,
7380
+ "put",
7381
+ level
7382
+ ),
6627
7383
  ...makeIndexOpsForDocument(
6628
7384
  normalizedPath,
6629
7385
  collection?.name,
@@ -6632,6 +7388,7 @@ var _indexContent = async (database, level, documentPaths, enqueueOps, collectio
6632
7388
  "put",
6633
7389
  level
6634
7390
  ),
7391
+ // folder indexes
6635
7392
  ...makeIndexOpsForDocument(
6636
7393
  normalizedPath,
6637
7394
  `${collection?.name}_${folderKey}`,
@@ -6655,8 +7412,7 @@ var _indexContent = async (database, level, documentPaths, enqueueOps, collectio
6655
7412
  throw new TinaFetchError(`Unable to seed ${filepath}`, {
6656
7413
  originalError: error,
6657
7414
  file: filepath,
6658
- collection: collection?.name,
6659
- stack: error.stack
7415
+ collection: collection?.name
6660
7416
  });
6661
7417
  }
6662
7418
  });
@@ -6686,6 +7442,7 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6686
7442
  throw new Error(`No indexDefinitions for collection ${collection.name}`);
6687
7443
  }
6688
7444
  }
7445
+ const collectionReferences = (await database.getCollectionReferences())?.[collection?.name];
6689
7446
  const tinaSchema = await database.getSchema();
6690
7447
  let templateInfo = null;
6691
7448
  if (collection) {
@@ -6709,6 +7466,14 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6709
7466
  item
6710
7467
  ) : item;
6711
7468
  await enqueueOps([
7469
+ ...makeRefOpsForDocument(
7470
+ itemKey,
7471
+ collection?.name,
7472
+ collectionReferences,
7473
+ aliasedData,
7474
+ "del",
7475
+ database.contentLevel
7476
+ ),
6712
7477
  ...makeIndexOpsForDocument(
6713
7478
  itemKey,
6714
7479
  collection.name,
@@ -6717,6 +7482,7 @@ var _deleteIndexContent = async (database, documentPaths, enqueueOps, collection
6717
7482
  "del",
6718
7483
  database.contentLevel
6719
7484
  ),
7485
+ // folder indexes
6720
7486
  ...makeIndexOpsForDocument(
6721
7487
  itemKey,
6722
7488
  `${collection?.name}_${folderKey}`,
@@ -6837,8 +7603,8 @@ import path6 from "path";
6837
7603
  import normalize from "normalize-path";
6838
7604
  var FilesystemBridge = class {
6839
7605
  constructor(rootPath, outputPath) {
6840
- this.rootPath = rootPath || "";
6841
- this.outputPath = outputPath || rootPath;
7606
+ this.rootPath = path6.resolve(rootPath);
7607
+ this.outputPath = outputPath ? path6.resolve(outputPath) : this.rootPath;
6842
7608
  }
6843
7609
  async glob(pattern, extension) {
6844
7610
  const basePath = path6.join(this.outputPath, ...pattern.split("/"));
@@ -6850,19 +7616,19 @@ var FilesystemBridge = class {
6850
7616
  }
6851
7617
  );
6852
7618
  const posixRootPath = normalize(this.outputPath);
6853
- return items.map((item) => {
6854
- return item.replace(posixRootPath, "").replace(/^\/|\/$/g, "");
6855
- });
7619
+ return items.map(
7620
+ (item) => item.substring(posixRootPath.length).replace(/^\/|\/$/g, "")
7621
+ );
6856
7622
  }
6857
7623
  async delete(filepath) {
6858
7624
  await fs2.remove(path6.join(this.outputPath, filepath));
6859
7625
  }
6860
7626
  async get(filepath) {
6861
- return fs2.readFileSync(path6.join(this.outputPath, filepath)).toString();
7627
+ return (await fs2.readFile(path6.join(this.outputPath, filepath))).toString();
6862
7628
  }
6863
7629
  async put(filepath, data, basePathOverride) {
6864
7630
  const basePath = basePathOverride || this.outputPath;
6865
- await fs2.outputFileSync(path6.join(basePath, filepath), data);
7631
+ await fs2.outputFile(path6.join(basePath, filepath), data);
6866
7632
  }
6867
7633
  };
6868
7634
  var AuditFileSystemBridge = class extends FilesystemBridge {
@@ -6932,17 +7698,26 @@ var IsomorphicBridge = class {
6932
7698
  getAuthor() {
6933
7699
  return {
6934
7700
  ...this.author,
6935
- timestamp: Math.round(new Date().getTime() / 1e3),
7701
+ timestamp: Math.round((/* @__PURE__ */ new Date()).getTime() / 1e3),
6936
7702
  timezoneOffset: 0
6937
7703
  };
6938
7704
  }
6939
7705
  getCommitter() {
6940
7706
  return {
6941
7707
  ...this.committer,
6942
- timestamp: Math.round(new Date().getTime() / 1e3),
7708
+ timestamp: Math.round((/* @__PURE__ */ new Date()).getTime() / 1e3),
6943
7709
  timezoneOffset: 0
6944
7710
  };
6945
7711
  }
7712
+ /**
7713
+ * Recursively populate paths matching `pattern` for the given `entry`
7714
+ *
7715
+ * @param pattern - pattern to filter paths by
7716
+ * @param entry - TreeEntry to start building list from
7717
+ * @param path - base path
7718
+ * @param results
7719
+ * @private
7720
+ */
6946
7721
  async listEntries({
6947
7722
  pattern,
6948
7723
  entry,
@@ -6975,6 +7750,15 @@ var IsomorphicBridge = class {
6975
7750
  });
6976
7751
  }
6977
7752
  }
7753
+ /**
7754
+ * For the specified path, returns an object with an array containing the parts of the path (pathParts)
7755
+ * and an array containing the WalkerEntry objects for the path parts (pathEntries). Any null elements in the
7756
+ * pathEntries are placeholders for non-existent entries.
7757
+ *
7758
+ * @param path - path being resolved
7759
+ * @param ref - ref to resolve path entries for
7760
+ * @private
7761
+ */
6978
7762
  async resolvePathEntries(path7, ref) {
6979
7763
  let pathParts = path7.split("/");
6980
7764
  const result = await git2.walk({
@@ -7005,6 +7789,17 @@ var IsomorphicBridge = class {
7005
7789
  }
7006
7790
  return { pathParts, pathEntries };
7007
7791
  }
7792
+ /**
7793
+ * Updates tree entry and associated parent tree entries
7794
+ *
7795
+ * @param existingOid - the existing OID
7796
+ * @param updatedOid - the updated OID
7797
+ * @param path - the path of the entry being updated
7798
+ * @param type - the type of the entry being updated (blob or tree)
7799
+ * @param pathEntries - parent path entries
7800
+ * @param pathParts - parent path parts
7801
+ * @private
7802
+ */
7008
7803
  async updateTreeHierarchy(existingOid, updatedOid, path7, type, pathEntries, pathParts) {
7009
7804
  const lastIdx = pathEntries.length - 1;
7010
7805
  const parentEntry = pathEntries[lastIdx];
@@ -7060,6 +7855,13 @@ var IsomorphicBridge = class {
7060
7855
  );
7061
7856
  }
7062
7857
  }
7858
+ /**
7859
+ * Creates a commit for the specified tree and updates the specified ref to point to the commit
7860
+ *
7861
+ * @param treeSha - sha of the new tree
7862
+ * @param ref - the ref that should be updated
7863
+ * @private
7864
+ */
7063
7865
  async commitTree(treeSha, ref) {
7064
7866
  const commitSha = await git2.writeCommit({
7065
7867
  ...this.isomorphicConfig,
@@ -7072,6 +7874,7 @@ var IsomorphicBridge = class {
7072
7874
  })
7073
7875
  ],
7074
7876
  message: this.commitMessage,
7877
+ // TODO these should be configurable
7075
7878
  author: this.getAuthor(),
7076
7879
  committer: this.getCommitter()
7077
7880
  }
@@ -7309,5 +8112,5 @@ export {
7309
8112
  transformDocument,
7310
8113
  transformDocumentIntoPayload
7311
8114
  };
7312
- //! Replaces _.flattenDeep()
7313
8115
  //! Replaces _.get()
8116
+ //! Replaces _.flattenDeep()