@tinacms/graphql 0.0.0-e0ddb8c-20241004065742 → 0.0.0-e27c017-20250619233313

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.
@@ -1,8 +1,3 @@
1
- /**
2
-
3
-
4
-
5
- */
6
- import { parseMDX, stringifyMDX } from '@tinacms/mdx';
1
+ import { parseMDX, serializeMDX } from '@tinacms/mdx';
7
2
  export { parseMDX };
8
- export { stringifyMDX };
3
+ export { serializeMDX };
@@ -2,7 +2,7 @@
2
2
 
3
3
  */
4
4
  import { Database } from '../database';
5
- import type { Collectable, Collection, TinaField, Template, TinaSchema } from '@tinacms/schema-tools';
5
+ import type { Collectable, Collection, Template, TinaField, TinaSchema } from '@tinacms/schema-tools';
6
6
  import type { GraphQLConfig } from '../types';
7
7
  interface ResolverConfig {
8
8
  config?: GraphQLConfig;
@@ -14,12 +14,13 @@ export declare const createResolver: (args: ResolverConfig) => Resolver;
14
14
  export declare const transformDocumentIntoPayload: (fullPath: string, rawData: {
15
15
  _collection: any;
16
16
  _template: any;
17
- }, tinaSchema: TinaSchema, config?: GraphQLConfig, isAudit?: boolean) => Promise<{
17
+ }, tinaSchema: TinaSchema, config?: GraphQLConfig, isAudit?: boolean, hasReferences?: boolean) => Promise<{
18
18
  _sys: {
19
19
  title: any;
20
20
  basename: string;
21
21
  filename: string;
22
22
  extension: string;
23
+ hasReferences: boolean;
23
24
  path: string;
24
25
  relativePath: string;
25
26
  breadcrumbs: string[];
@@ -39,6 +40,17 @@ export declare const transformDocumentIntoPayload: (fullPath: string, rawData: {
39
40
  __typename: string;
40
41
  id: string;
41
42
  }>;
43
+ /**
44
+ * Updates a property in an object using a JSONPath.
45
+ * @param obj - The object to update.
46
+ * @param path - The JSONPath string.
47
+ * @param newValue - The new value to set at the specified path.
48
+ * @returns the updated object.
49
+ */
50
+ export declare const updateObjectWithJsonPath: (obj: any, path: string, oldValue: any, newValue: any) => {
51
+ object: any;
52
+ updated: boolean;
53
+ };
42
54
  /**
43
55
  * The resolver provides functions for all possible types of lookup
44
56
  * values and retrieves them from the database
@@ -62,10 +74,10 @@ export declare class Resolver {
62
74
  name: string;
63
75
  }[];
64
76
  }[];
65
- format?: "json" | "md" | "markdown" | "mdx" | "yaml" | "yml" | "toml";
77
+ format?: import("@tinacms/schema-tools").ContentFormat;
66
78
  ui?: import("@tinacms/schema-tools").UICollection;
67
79
  defaultItem?: import("@tinacms/schema-tools").DefaultItem<Record<string, any>>;
68
- frontmatterFormat?: "yaml" | "toml" | "json";
80
+ frontmatterFormat?: import("@tinacms/schema-tools").ContentFrontmatterFormat;
69
81
  frontmatterDelimiters?: [string, string] | string;
70
82
  match?: {
71
83
  include?: string;
@@ -90,10 +102,10 @@ export declare class Resolver {
90
102
  name: string;
91
103
  }[];
92
104
  }[];
93
- format?: "json" | "md" | "markdown" | "mdx" | "yaml" | "yml" | "toml";
105
+ format?: import("@tinacms/schema-tools").ContentFormat;
94
106
  ui?: import("@tinacms/schema-tools").UICollection;
95
107
  defaultItem?: import("@tinacms/schema-tools").DefaultItem<Record<string, any>>;
96
- frontmatterFormat?: "yaml" | "toml" | "json";
108
+ frontmatterFormat?: import("@tinacms/schema-tools").ContentFrontmatterFormat;
97
109
  frontmatterDelimiters?: [string, string] | string;
98
110
  match?: {
99
111
  include?: string;
@@ -117,6 +129,7 @@ export declare class Resolver {
117
129
  basename: string;
118
130
  filename: string;
119
131
  extension: string;
132
+ hasReferences: boolean;
120
133
  path: string;
121
134
  relativePath: string;
122
135
  breadcrumbs: string[];
@@ -140,12 +153,16 @@ export declare class Resolver {
140
153
  name: any;
141
154
  path: any;
142
155
  }>;
143
- getDocument: (fullPath: unknown) => Promise<{
156
+ getDocument: (fullPath: unknown, opts?: {
157
+ collection?: Collection<true>;
158
+ checkReferences?: boolean;
159
+ }) => Promise<{
144
160
  _sys: {
145
161
  title: any;
146
162
  basename: string;
147
163
  filename: string;
148
164
  extension: string;
165
+ hasReferences: boolean;
149
166
  path: string;
150
167
  relativePath: string;
151
168
  breadcrumbs: string[];
@@ -184,6 +201,7 @@ export declare class Resolver {
184
201
  basename: string;
185
202
  filename: string;
186
203
  extension: string;
204
+ hasReferences: boolean;
187
205
  path: string;
188
206
  relativePath: string;
189
207
  breadcrumbs: string[];
@@ -215,6 +233,7 @@ export declare class Resolver {
215
233
  basename: string;
216
234
  filename: string;
217
235
  extension: string;
236
+ hasReferences: boolean;
218
237
  path: string;
219
238
  relativePath: string;
220
239
  breadcrumbs: string[];
@@ -255,6 +274,7 @@ export declare class Resolver {
255
274
  basename: string;
256
275
  filename: string;
257
276
  extension: string;
277
+ hasReferences: boolean;
258
278
  path: string;
259
279
  relativePath: string;
260
280
  breadcrumbs: string[];
@@ -285,6 +305,7 @@ export declare class Resolver {
285
305
  basename: string;
286
306
  filename: string;
287
307
  extension: string;
308
+ hasReferences: boolean;
288
309
  path: string;
289
310
  relativePath: string;
290
311
  breadcrumbs: string[];
@@ -325,6 +346,20 @@ export declare class Resolver {
325
346
  endCursor: string;
326
347
  };
327
348
  }>;
349
+ /**
350
+ * Checks if a document has references to it
351
+ * @param id The id of the document to check for references
352
+ * @param c The collection to check for references
353
+ * @returns true if the document has references, false otherwise
354
+ */
355
+ private hasReferences;
356
+ /**
357
+ * Finds references to a document
358
+ * @param id the id of the document to find references to
359
+ * @param c the collection to find references in
360
+ * @returns a map of references to the document
361
+ */
362
+ private findReferences;
328
363
  private buildFieldMutations;
329
364
  /**
330
365
  * A mutation looks nearly identical between updateDocument:
@@ -1,10 +1,10 @@
1
1
  /**
2
2
 
3
3
  */
4
- import type { GraphQLConfig } from '../types';
5
4
  import type { Schema } from '@tinacms/schema-tools';
5
+ import type { GraphQLConfig } from '../types';
6
6
  /**
7
- * Strips away the Tina Cloud Asset URL from an `image` value
7
+ * Strips away the TinaCloud Asset URL from an `image` value
8
8
  *
9
9
  * @param {string | string[]} value
10
10
  * @param {GraphQLConfig} config
@@ -12,7 +12,7 @@ import type { Schema } from '@tinacms/schema-tools';
12
12
  */
13
13
  export declare const resolveMediaCloudToRelative: (value: string | string[], config: GraphQLConfig, schema: Schema<true>) => string | string[];
14
14
  /**
15
- * Adds Tina Cloud Asset URL to an `image` value
15
+ * Adds TinaCloud Asset URL to an `image` value
16
16
  *
17
17
  * @param {string | string[]} value
18
18
  * @param {GraphQLConfig} config
@@ -1,6 +1,3 @@
1
- /**
2
-
3
- */
4
1
  import { TinaSchema, Schema } from '@tinacms/schema-tools';
5
2
  export declare const createSchema: ({ schema, flags, }: {
6
3
  schema: Schema;
@@ -1,6 +1,3 @@
1
- /**
2
-
3
- */
4
1
  import { type Schema, type Collection } from '@tinacms/schema-tools';
5
2
  export declare const validateSchema: (schema: Schema) => Promise<{
6
3
  collections: Collection<true>[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinacms/graphql",
3
- "version": "0.0.0-e0ddb8c-20241004065742",
3
+ "version": "0.0.0-e27c017-20250619233313",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "typings": "dist/index.d.ts",
@@ -26,26 +26,26 @@
26
26
  "@iarna/toml": "^2.2.5",
27
27
  "abstract-level": "^1.0.4",
28
28
  "date-fns": "^2.30.0",
29
- "fast-glob": "^3.3.2",
30
- "fs-extra": "^11.2.0",
29
+ "fast-glob": "^3.3.3",
30
+ "fs-extra": "^11.3.0",
31
31
  "glob-parent": "^6.0.2",
32
32
  "graphql": "15.8.0",
33
33
  "gray-matter": "^4.0.3",
34
- "isomorphic-git": "^1.27.1",
34
+ "isomorphic-git": "^1.29.0",
35
35
  "js-sha1": "^0.6.0",
36
36
  "js-yaml": "^3.14.1",
37
- "jsonpath-plus": "^6.0.1",
37
+ "jsonpath-plus": "10.1.0",
38
38
  "lodash.clonedeep": "^4.5.0",
39
39
  "lodash.set": "^4.3.2",
40
40
  "lodash.uniqby": "^4.7.0",
41
41
  "many-level": "^2.0.0",
42
42
  "micromatch": "4.0.8",
43
43
  "normalize-path": "^3.0.0",
44
- "readable-stream": "^4.5.2",
44
+ "readable-stream": "^4.7.0",
45
45
  "scmp": "^2.1.0",
46
46
  "yup": "^0.32.11",
47
- "@tinacms/schema-tools": "0.0.0-e0ddb8c-20241004065742",
48
- "@tinacms/mdx": "0.0.0-e0ddb8c-20241004065742"
47
+ "@tinacms/mdx": "0.0.0-e27c017-20250619233313",
48
+ "@tinacms/schema-tools": "0.0.0-e27c017-20250619233313"
49
49
  },
50
50
  "publishConfig": {
51
51
  "registry": "https://registry.npmjs.org"
@@ -59,33 +59,30 @@
59
59
  "@types/estree": "^0.0.50",
60
60
  "@types/express": "^4.17.21",
61
61
  "@types/fs-extra": "^9.0.13",
62
- "@types/jest": "^26.0.24",
63
62
  "@types/js-yaml": "^3.12.10",
64
63
  "@types/lodash.camelcase": "^4.3.9",
65
64
  "@types/lodash.upperfirst": "^4.3.9",
66
65
  "@types/lru-cache": "^5.1.1",
67
66
  "@types/mdast": "^3.0.15",
68
67
  "@types/micromatch": "^4.0.9",
69
- "@types/node": "^22.7.4",
68
+ "@types/node": "^22.13.1",
70
69
  "@types/normalize-path": "^3.0.2",
71
70
  "@types/ws": "^7.4.7",
72
71
  "@types/yup": "^0.29.14",
73
- "jest": "^29.7.0",
74
- "jest-diff": "^29.7.0",
75
72
  "jest-file-snapshot": "^0.5.0",
76
- "jest-matcher-utils": "^29.7.0",
77
73
  "memory-level": "^1.0.0",
78
- "nodemon": "3.1.4",
79
- "typescript": "^5.6.2",
80
- "@tinacms/schema-tools": "0.0.0-e0ddb8c-20241004065742",
81
- "@tinacms/scripts": "1.2.3"
74
+ "typescript": "^5.7.3",
75
+ "vite": "^4.5.9",
76
+ "vitest": "^0.32.4",
77
+ "zod": "^3.24.2",
78
+ "@tinacms/schema-tools": "0.0.0-e27c017-20250619233313",
79
+ "@tinacms/scripts": "1.3.5"
82
80
  },
83
81
  "scripts": {
84
82
  "types": "pnpm tsc",
85
83
  "build": "tinacms-scripts build",
86
84
  "docs": "pnpm typedoc",
87
- "serve": "pnpm nodemon dist/server.js",
88
- "test": "jest",
89
- "test-watch": "jest --watch"
85
+ "test": "vitest run",
86
+ "test-watch": "vitest"
90
87
  }
91
88
  }
package/readme.md DELETED
@@ -1,194 +0,0 @@
1
- ## Getting started
2
-
3
- There's a serve and watch command, which are separate for now:
4
-
5
- ```sh
6
- // terminal 1
7
- cd packages/tina-graphql
8
- yarn watch
9
-
10
- // terminal 2
11
- cd packages/tina-graphql
12
- yarn serve
13
- ```
14
-
15
- You can consume this from the `graphiql` app:
16
-
17
- ```sh
18
- // terminal 3
19
- cd apps/graphiql
20
- yarn start
21
- ```
22
-
23
- Note that this app doesn't use anything from the `client` package right now, it's just an interactive tool to see how things move from the graphql server into tina. That process has been improved in this package but will need to be merged back into the `client` package before this is usable.
24
-
25
- ### Running queries
26
-
27
- By default the app will redirect to `project1` and display the default query generated from the `graphql-helpers` library - which consumes the fixtures from the `project1` folder the the `gql` package, any number of fixtures can be used if you want to add your own, just ensure the `server.ts` file knows about them.
28
-
29
- When you run the initial query, you should see the result along with the Tina sidebar toggle, this indicates that the Tina form has now been populated with the query values. If you change some values around and hit submit, the `onSubmit` function will populate the GraphiQL editor instead of sending it off to the server, you can play around with the mutation before sending it off if you'd like.
30
-
31
- ### Tests
32
-
33
- The most valuable test right now is the `builder.spec.ts`, it's sort of an integration of all the field-level builders. There are also field-level build tests, but not resolvers ones just yet. If you're making changes to the builder just run `yarn test-watch` and hit `p` to provide a pattern, then type "builder", this will isolate that test and if it's passing you probably didn't break anything.
34
-
35
- ## Architecture
36
-
37
- ### Builder
38
-
39
- The builder service is responsible for building out the entire GraphQL schema for a given `.tina` config. This service can run at any time (but needs to be re-run on each schema change) and it's output is a GraphQL schema which can be stored in the schema definition language (SDL) as a string in a database record or as a `.graphql` file. At the top of the schema is a `document` query, this query returns the document, which can be one of any number of templates defined in the `.tina` config. From there, each field in the given template is used to build out the rest of the schema, so each template field is built by the `type` in it's definition
40
-
41
- #### Field-level builders
42
-
43
- Field-level builders take a field definition and produce 4 different GraphQL types:
44
-
45
- ##### `field`
46
-
47
- Builds the type which fits into Tina's field definition shape:
48
-
49
- Given:
50
-
51
- ```yaml
52
- name: Title
53
- label: title
54
- type: text
55
- ```
56
-
57
- ```js
58
- text.build.field({ cache, field });
59
- ```
60
-
61
- Produces
62
-
63
- ```graphql
64
- type TextField {
65
- name: String
66
- label: String
67
- component: String
68
- description: String
69
- }
70
- ```
71
-
72
- ##### `initialValue`
73
-
74
- Tina fields need an initial value when editing existing data. This builder is responsible for providing the shape of that value.
75
-
76
- For most fields this is the same value as `value` - but if you picture the schema as a "graph" - you can see how the "value" of a document reference (ie. a Post has an Author) is not helpful to Tina. Tina only cares about the stored document value of the reference (in this case `/path/to/author.md`) so it's the `initialValue`'s role to provide what makes sense to Tina, regardless of the schema's relationships.
77
-
78
- ##### `value`
79
-
80
- The value of the field, it's the role of this function to provide the shape of the data we should expect for a fully resolved graph.
81
-
82
- For `block` fields, this looks like an array of different shapes, which means it's the `blocks.build.value` function's responsibility to return a `union` array.
83
-
84
- ##### `input`
85
-
86
- When a mutation is made, the shape of this mutation needs to fit the shape create by this function.
87
-
88
- ### Resolvers
89
-
90
- `resolvers` can be thought of as the runtime siblings to `builders`. While it's the job of builders to define the "graph", the resolvers are responsible for taking raw values (like those from a `.md` file) and shaping them so they fit the schema.
91
-
92
- #### Field-level resolvers
93
-
94
- Again, similar to field-level builders, most of the work for resolving the data is passed on to the appropriate field to handle. So if you have a document like so:
95
-
96
- ```md
97
- ---
98
- title: Hello, World!
99
- author: /authors/homer.md
100
- ---
101
- ```
102
-
103
- It's template definition might look like:
104
-
105
- ```yaml
106
- label: Post
107
- ---
108
- fields:
109
- - name: title
110
- label: Title
111
- type: text
112
- - name: author
113
- label: Author
114
- type: select
115
- config:
116
- source:
117
- type: pages
118
- section: authors
119
- ```
120
-
121
- The `text.resolver` object will be responsible for resolving the values related to `title`:
122
-
123
- ##### `field`
124
-
125
- The `field` resolver provides the appropriate values for it's `field` builder counterpart. In the example above the `text.resolve.field` function would return:
126
-
127
- ```json
128
- {
129
- "name": "title",
130
- "label": "Title",
131
- "component": "text"
132
- }
133
- ```
134
-
135
- This would then be passed on to Tina for rendering on the client.
136
-
137
- ##### `initialValue`
138
-
139
- In the example above the `text.resolve.initialValue` would return "Hello, World!"
140
-
141
- For blocks we need to return the object along with a `_template` key, this is used downstream to disambiguate which template the value comes from.
142
-
143
- ##### `value`
144
-
145
- In the example above the `text.resolve.value` would return "Hello, World!", and again, for document references this would return the entire document being referenced, which may or may not be used depending on the graph fields requested
146
-
147
- ##### `input`
148
-
149
- Input resolvers don't do much (except in the case of blocks described later), since the GraphQL mutataion payload has all the necessary information, we just pass the value into these resolvers as a runtime type-check. In the future, this is where field-level validations can take place.
150
-
151
- **Caveats with `blocks`**: `blocks` values are an array of unlike objects, meaning in order to enforce type-safe requests coming into the server, we need to use a somewhat awkward pattern ([read more about the trade-offs here](https://github.com/graphql/graphql-spec/blob/master/rfcs/InputUnion.md#-5-one-of-tagged-union)) which we sort of need to rearrange once it hits the server.
152
-
153
- ## Architecture Diagram
154
-
155
- <iframe style="border:none" width="700" height="350" src="https://whimsical.com/embed/Kh28ULaAYKPRpeCLm3VG63@2Ux7TurymMtzhxz2sLxX"></iframe>
156
-
157
- ## Caveats
158
-
159
- ### Why do we use `GraphQLUnion` instead of `GraphQLInterface` for fields?
160
-
161
- Since `component`, `label`, & `name` are common across all fields, we'd only use a fragment to gather what's unique to that field type, so field definitions using an interface would allow our queries to look like this:
162
-
163
- ```graphql
164
- fields {
165
- component
166
- label
167
- name
168
- ...on SelectField {
169
- options
170
- }
171
- }
172
- ```
173
-
174
- Instead, we use a union - which requires us to load each key inside it's respective fragment:
175
-
176
- ```graphql
177
- fields {
178
- ... on TextareaField {
179
- name
180
- label
181
- component
182
- }
183
- ... on SelectField {
184
- name
185
- label
186
- component
187
- options
188
- }
189
- }
190
- ```
191
-
192
- A GraphQL interface allows you to define a heterogeneous set of types, which have some fields in common. This is a textbook usecase for interfaces, and it's something that could change in the future. But the current reason we're using unions is because unions are exhaustive, and they allow us to scope down the possible field types for a given set of fields.
193
-
194
- An interface would be too broad for our needs, a collection of fields should only contain the types which are possible for that given template config. So while an `interface` would allow us to present **all** possible field types, a `union` gives us the ability to scope down the field list to only allow what the template defines. Using `unions` forces us to be explicit about that in a way that's clear (note: it may be possible to do this with interfaces but there would end up being an interface for each collection of possible fields - making the `interface` term somewhat misleading). Using unions also allows our auto-querybuilder to know that they have populated all possible types of a field, something that seems like it might be more difficult with interfaces.