@tinacms/app 0.0.12 → 0.0.13

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,574 +0,0 @@
1
- /**
2
- Copyright 2021 Forestry.io Holdings, Inc.
3
- Licensed under the Apache License, Version 2.0 (the "License");
4
- you may not use this file except in compliance with the License.
5
- You may obtain a copy of the License at
6
- http://www.apache.org/licenses/LICENSE-2.0
7
- Unless required by applicable law or agreed to in writing, software
8
- distributed under the License is distributed on an "AS IS" BASIS,
9
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
- See the License for the specific language governing permissions and
11
- limitations under the License.
12
- */
13
-
14
- import * as G from 'graphql'
15
- import * as util from './formify-utils'
16
- import type { DocumentBlueprint, BlueprintPath } from './types'
17
-
18
- type VisitorType = G.Visitor<G.ASTKindToNode, G.ASTNode>
19
-
20
- const NOOP = 'This is either an error or is not yet supported'
21
- const UNEXPECTED =
22
- 'Formify encountered an unexpected error, please contact support'
23
- const EDGES_NODE_NAME = 'edges'
24
- const NODE_NAME = 'node'
25
- const COLLECTION_FIELD_NAME = 'collection'
26
- const COLLECTIONS_FIELD_NAME = 'collections'
27
- const COLLECTIONS_DOCUMENTS_NAME = 'documents'
28
-
29
- export const formify = async ({
30
- schema,
31
- optimizedDocumentNode,
32
- }: {
33
- schema: G.GraphQLSchema
34
- optimizedDocumentNode: G.DocumentNode
35
- }): Promise<{
36
- formifiedQuery: G.DocumentNode
37
- blueprints: DocumentBlueprint[]
38
- }> => {
39
- const blueprints: DocumentBlueprint[] = []
40
- const visitor: VisitorType = {
41
- OperationDefinition: (node) => {
42
- if (!node.name) {
43
- return {
44
- ...node,
45
- name: {
46
- kind: 'Name',
47
- // FIXME: add some sort of uuid to this
48
- value: `QueryOperation`,
49
- },
50
- }
51
- }
52
- return node
53
- },
54
- }
55
- const documentNodeWithName = G.visit(optimizedDocumentNode, visitor)
56
- const optimizedQuery = documentNodeWithName
57
- const typeInfo = new G.TypeInfo(schema)
58
-
59
- const formifyConnection = ({
60
- parentType,
61
- selectionNode,
62
- path,
63
- isTopLevel,
64
- }: {
65
- parentType: G.GraphQLOutputType
66
- selectionNode: G.FieldNode
67
- path?: { name: string; alias: string }[]
68
- isTopLevel: boolean
69
- }): G.SelectionNode => {
70
- return {
71
- ...selectionNode,
72
- selectionSet: {
73
- kind: 'SelectionSet' as const,
74
- selections: selectionNode.selectionSet.selections.map(
75
- (selectionNode2) => {
76
- switch (selectionNode2.kind) {
77
- case 'Field':
78
- if (selectionNode2.name.value === EDGES_NODE_NAME) {
79
- const edgeField = util.getObjectField(
80
- parentType,
81
- selectionNode2
82
- )
83
- const edgesPath = util.buildPath({
84
- fieldNode: selectionNode2,
85
- type: edgeField.type,
86
- path,
87
- })
88
- return {
89
- ...selectionNode2,
90
- selectionSet: {
91
- kind: 'SelectionSet' as const,
92
- selections: selectionNode2.selectionSet.selections.map(
93
- (subSelectionNode) => {
94
- switch (subSelectionNode.kind) {
95
- case 'Field':
96
- if (subSelectionNode.name.value === NODE_NAME) {
97
- const nodeField = util.getObjectField(
98
- edgeField.type,
99
- subSelectionNode
100
- )
101
-
102
- return formifyFieldNodeDocument({
103
- fieldNode: subSelectionNode,
104
- type: nodeField.type,
105
- path: util.buildPath({
106
- fieldNode: subSelectionNode,
107
- type: nodeField.type,
108
- path: edgesPath,
109
- }),
110
- showInSidebar: false,
111
- isTopLevel,
112
- })
113
- } else {
114
- return subSelectionNode
115
- }
116
- default:
117
- throw new FormifyError('NOOP')
118
- }
119
- }
120
- ),
121
- },
122
- }
123
- }
124
- return selectionNode2
125
- default:
126
- throw new FormifyError('UNEXPECTED')
127
- }
128
- }
129
- ),
130
- },
131
- }
132
- }
133
-
134
- type SelectionDocument<T extends G.InlineFragmentNode | G.FieldNode> =
135
- T extends G.InlineFragmentNode
136
- ? {
137
- inlineFragmentNode: T
138
- type: G.GraphQLOutputType
139
- path: BlueprintPath[]
140
- showInSidebar: boolean
141
- isTopLevel: boolean
142
- }
143
- : {
144
- fieldNode: T
145
- type: G.GraphQLOutputType
146
- path: BlueprintPath[]
147
- showInSidebar: boolean
148
- isTopLevel: boolean
149
- }
150
-
151
- function formifyInlineFragmentDocument({
152
- inlineFragmentNode,
153
- type,
154
- path,
155
- showInSidebar = false,
156
- isTopLevel = false,
157
- }: SelectionDocument<G.InlineFragmentNode>): G.InlineFragmentNode {
158
- return formifyDocument({
159
- selection: inlineFragmentNode,
160
- type,
161
- path,
162
- showInSidebar,
163
- isTopLevel,
164
- })
165
- }
166
- function formifyFieldNodeDocument({
167
- fieldNode,
168
- type,
169
- path,
170
- showInSidebar = false,
171
- isTopLevel = false,
172
- }: SelectionDocument<G.FieldNode>): G.FieldNode {
173
- return formifyDocument({
174
- selection: fieldNode,
175
- type,
176
- path,
177
- showInSidebar,
178
- isTopLevel,
179
- })
180
- }
181
-
182
- function formifyDocument<T extends G.InlineFragmentNode | G.FieldNode>({
183
- selection,
184
- type,
185
- path,
186
- showInSidebar = false,
187
- isTopLevel = false,
188
- }: {
189
- selection: T
190
- type: G.GraphQLOutputType
191
- path: BlueprintPath[]
192
- showInSidebar: boolean
193
- isTopLevel: boolean
194
- }): T {
195
- let extraFields: G.SelectionNode[] = []
196
-
197
- const hasDataJSONField = false
198
- let hasValuesField = false
199
- let shouldFormify = false
200
- selection.selectionSet.selections.forEach((selection) => {
201
- /**
202
- * This check makes sure we don't formify on inline fragments,
203
- * so shouldFormify only returns true for sub selection, which will be
204
- * a document
205
- * ```
206
- * {
207
- * document(relativePath: $relativePath) {
208
- * # don't want _sys and _values here
209
- * ...on Author {
210
- * # we want them here
211
- * name
212
- * }
213
- * }
214
- * }
215
- */
216
- if (selection.kind === 'Field') {
217
- shouldFormify = true
218
- if (selection.name.value === '_values') {
219
- hasValuesField = true
220
- }
221
- }
222
- })
223
-
224
- if (shouldFormify) {
225
- const namedType = G.getNamedType(type)
226
- blueprints.push({
227
- id: util.getBlueprintId(path),
228
- path,
229
- // This just grabs the inner portion of the query, so if it was post(relativePath: ...) { ... }, it grabs the {...} portion
230
- // and sets it as an inline fragment (...on Post {...})
231
- selection: {
232
- kind: 'InlineFragment',
233
- typeCondition: {
234
- kind: 'NamedType',
235
- name: { kind: 'Name', value: namedType.name },
236
- },
237
- selectionSet: selection.selectionSet,
238
- },
239
- fields: [],
240
- showInSidebar: showInSidebar,
241
- hasDataJSONField,
242
- hasValuesField,
243
- isTopLevel,
244
- })
245
- /**
246
- * This is the primary purpose of formify, adding fields like
247
- * `form`, `values` and `_internalSys`
248
- */
249
- extraFields = util.metaFields
250
- }
251
-
252
- return {
253
- ...selection,
254
- selectionSet: {
255
- kind: 'SelectionSet',
256
- selections: [
257
- ...selection.selectionSet.selections.map((selectionNode) => {
258
- switch (selectionNode.kind) {
259
- /**
260
- * An inline fragment will be present when the document result is polymorphic,
261
- * when that's the case, we need to "step down" one level with a disambiguator
262
- * (eg. ...on PageDocument), then run formifyFieldNodeDocument again at this lower tier
263
- */
264
- case 'InlineFragment': {
265
- /**
266
- * This is a somewhat special use-case for node(id: "")
267
- */
268
- const namedType = G.getNamedType(type)
269
- if (G.isInterfaceType(namedType)) {
270
- const subType = schema
271
- .getImplementations(namedType)
272
- .objects.find(
273
- (item) =>
274
- item.name === selectionNode.typeCondition.name.value
275
- )
276
- return formifyInlineFragmentDocument({
277
- inlineFragmentNode: selectionNode,
278
- type: subType,
279
- path,
280
- showInSidebar: true,
281
- isTopLevel: true,
282
- })
283
- }
284
- return formifyInlineFragmentNode({
285
- inlineFragmentNode: selectionNode,
286
- parentType: type,
287
- path,
288
- isTopLevel: true,
289
- })
290
- }
291
- case 'Field': {
292
- return formifyFieldNode({
293
- fieldNode: selectionNode,
294
- parentType: type,
295
- path,
296
- })
297
- }
298
- default:
299
- throw new FormifyError('UNEXPECTED')
300
- }
301
- }),
302
- ...extraFields,
303
- ],
304
- },
305
- }
306
- }
307
-
308
- const formifyFieldNode = ({
309
- fieldNode,
310
- parentType,
311
- path,
312
- }: {
313
- fieldNode: G.FieldNode
314
- parentType: G.GraphQLOutputType
315
- path: BlueprintPath[]
316
- }) => {
317
- if (fieldNode.name.value === '__typename') {
318
- return fieldNode
319
- }
320
- const field = util.getObjectField(parentType, fieldNode)
321
-
322
- if (!field) {
323
- return fieldNode
324
- }
325
-
326
- const fieldPath = util.buildPath({
327
- fieldNode,
328
- type: field.type,
329
- parentTypename: G.getNamedType(parentType).name,
330
- path,
331
- })
332
- const blueprint = blueprints.find(
333
- (blueprint) => blueprint.id === util.getRelativeBlueprint(fieldPath)
334
- )
335
- /**
336
- * This would be a field like sys.filename
337
- * which has no need to be formified, so exit.
338
- */
339
- if (!blueprint) {
340
- return fieldNode
341
- }
342
-
343
- if (util.isSysField(fieldNode)) {
344
- return fieldNode
345
- }
346
-
347
- blueprint.fields.push({
348
- id: util.getBlueprintId(fieldPath),
349
- documentBlueprintId: blueprint.id,
350
- path: fieldPath,
351
- })
352
-
353
- if (util.isScalarType(field.type)) {
354
- return fieldNode
355
- }
356
-
357
- return {
358
- ...fieldNode,
359
- selectionSet: {
360
- kind: 'SelectionSet',
361
- selections: [
362
- ...fieldNode.selectionSet.selections.map((selectionNode) => {
363
- switch (selectionNode.kind) {
364
- case 'Field': {
365
- return formifyFieldNode({
366
- fieldNode: selectionNode,
367
- parentType: field.type,
368
- path: fieldPath,
369
- })
370
- }
371
- case 'InlineFragment': {
372
- return formifyInlineFragmentNode({
373
- inlineFragmentNode: selectionNode,
374
- parentType: field.type,
375
- path: fieldPath,
376
- })
377
- }
378
- default:
379
- throw new FormifyError(
380
- 'UNEXPECTED',
381
- `selection ${selectionNode.kind}`
382
- )
383
- }
384
- }),
385
- ],
386
- },
387
- }
388
- }
389
-
390
- const formifyInlineFragmentNode = ({
391
- inlineFragmentNode,
392
- parentType,
393
- path,
394
- isTopLevel = false,
395
- }: {
396
- inlineFragmentNode: G.InlineFragmentNode
397
- parentType: G.GraphQLOutputType
398
- path: BlueprintPath[]
399
- isTopLevel: boolean
400
- }) => {
401
- const type = util.getSelectedUnionType(parentType, inlineFragmentNode)
402
- if (!type) {
403
- return inlineFragmentNode
404
- }
405
-
406
- /**
407
- * FIXME: this returns true for template collections on
408
- * both the query field level and the inline fragment level so
409
- * ```graphql
410
- * query {
411
- * page(relativePath: "") {
412
- * ...on MyBlockPage {
413
- * title
414
- * }
415
- * }
416
- * }
417
- * ```
418
- * Both the `page` field and the `...on MyBlockPage` field
419
- * are considered blueprints. I'm pretty sure we only want
420
- * the top-level one.
421
- */
422
- if (util.isFormifiableDocument(type)) {
423
- return formifyInlineFragmentDocument({
424
- inlineFragmentNode: inlineFragmentNode,
425
- type,
426
- path,
427
- showInSidebar: false,
428
- isTopLevel,
429
- })
430
- }
431
-
432
- return {
433
- ...inlineFragmentNode,
434
- selectionSet: {
435
- kind: 'SelectionSet' as const,
436
- selections: inlineFragmentNode.selectionSet.selections.map(
437
- (selectionNode) => {
438
- switch (selectionNode.kind) {
439
- case 'Field':
440
- return formifyFieldNode({
441
- fieldNode: selectionNode,
442
- parentType: type,
443
- path,
444
- })
445
- default:
446
- throw new FormifyError(
447
- 'UNEXPECTED',
448
- `selection ${selectionNode.kind}`
449
- )
450
- }
451
- }
452
- ),
453
- },
454
- }
455
- }
456
-
457
- const formifiedQuery: G.DocumentNode = {
458
- kind: 'Document',
459
- definitions: optimizedQuery.definitions.map((definition) => {
460
- typeInfo.enter(definition)
461
- util.ensureOperationDefinition(definition)
462
- const parentType = typeInfo.getType()
463
-
464
- return {
465
- ...definition,
466
- selectionSet: {
467
- kind: 'SelectionSet',
468
- selections: definition.selectionSet.selections.map(
469
- (selectionNode) => {
470
- switch (selectionNode.kind) {
471
- case 'Field':
472
- const field = util.getObjectField(parentType, selectionNode)
473
- const path = util.buildPath({
474
- fieldNode: selectionNode,
475
- type: field.type,
476
- })
477
- if (util.isFormifiableDocument(field.type)) {
478
- return formifyFieldNodeDocument({
479
- fieldNode: selectionNode,
480
- type: field.type,
481
- path,
482
- // NOTE: for now, only top-level, non-list queries are
483
- // shown in the sidebar
484
- showInSidebar: true,
485
- isTopLevel: true,
486
- })
487
- } else if (util.isConnectionField(field.type)) {
488
- return formifyConnection({
489
- parentType: field.type,
490
- selectionNode,
491
- path,
492
- isTopLevel: true,
493
- })
494
- }
495
-
496
- /**
497
- * `getCollection` and `getColletions` include a `documents` list field
498
- * which can possibly be formified.
499
- */
500
- if (
501
- selectionNode.name.value === COLLECTION_FIELD_NAME ||
502
- selectionNode.name.value === COLLECTIONS_FIELD_NAME
503
- ) {
504
- const path = util.buildPath({
505
- fieldNode: selectionNode,
506
- type: field.type,
507
- })
508
- return {
509
- ...selectionNode,
510
- selectionSet: {
511
- kind: 'SelectionSet',
512
- selections: selectionNode.selectionSet.selections.map(
513
- (subSelectionNode) => {
514
- switch (subSelectionNode.kind) {
515
- case 'Field':
516
- if (
517
- subSelectionNode.name.value ===
518
- COLLECTIONS_DOCUMENTS_NAME
519
- ) {
520
- const subField = util.getObjectField(
521
- field.type,
522
- subSelectionNode
523
- )
524
- return formifyConnection({
525
- parentType: subField.type,
526
- selectionNode: subSelectionNode,
527
- path: util.buildPath({
528
- fieldNode: subSelectionNode,
529
- type: subField.type,
530
- path,
531
- }),
532
- isTopLevel: true,
533
- })
534
- }
535
- return subSelectionNode
536
- default:
537
- throw new FormifyError('NOOP')
538
- }
539
- }
540
- ),
541
- },
542
- }
543
- }
544
- throw new FormifyError('NOOP')
545
- default:
546
- throw new FormifyError('UNEXPECTED')
547
- }
548
- }
549
- ),
550
- },
551
- }
552
- }),
553
- }
554
- return { formifiedQuery, blueprints }
555
- }
556
-
557
- class FormifyError extends Error {
558
- constructor(code: 'NOOP' | 'UNEXPECTED', details?: string) {
559
- let message
560
- switch (code) {
561
- case 'NOOP':
562
- message = NOOP
563
- break
564
- case 'UNEXPECTED':
565
- message = UNEXPECTED
566
- break
567
- default:
568
- message = ''
569
- break
570
- }
571
- super(`${message} ${details || ''}`)
572
- this.name = 'FormifyError'
573
- }
574
- }
@@ -1,67 +0,0 @@
1
- /**
2
- Copyright 2021 Forestry.io Holdings, Inc.
3
- Licensed under the Apache License, Version 2.0 (the "License");
4
- you may not use this file except in compliance with the License.
5
- You may obtain a copy of the License at
6
- http://www.apache.org/licenses/LICENSE-2.0
7
- Unless required by applicable law or agreed to in writing, software
8
- distributed under the License is distributed on an "AS IS" BASIS,
9
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
- See the License for the specific language governing permissions and
11
- limitations under the License.
12
- */
13
-
14
- import { formify } from './formify'
15
- import * as util from './util'
16
-
17
- import type { FormifiedDocumentNode, DocumentBlueprint } from './types'
18
- import { FormNode } from './types'
19
-
20
- export { formify }
21
-
22
- export const buildForms = ({ addForm, result, blueprints }) => {
23
- blueprints.map((blueprint) => {
24
- const responseAtBlueprint =
25
- util.getValueForBlueprint<FormifiedDocumentNode>(
26
- result,
27
- util.getBlueprintAliasPath(blueprint)
28
- )
29
- const location = []
30
- const findFormNodes = (
31
- res: typeof responseAtBlueprint,
32
- location: number[]
33
- ): void => {
34
- if (Array.isArray(res)) {
35
- res.forEach((item, index) => {
36
- if (Array.isArray(item)) {
37
- findFormNodes(item, [...location, index])
38
- } else {
39
- if (item) {
40
- addForm(buildFormNode(blueprint, item, [...location, index]))
41
- }
42
- }
43
- })
44
- } else {
45
- if (res) {
46
- addForm(buildFormNode(blueprint, res, location))
47
- }
48
- }
49
- }
50
- findFormNodes(responseAtBlueprint, location)
51
- })
52
- }
53
-
54
- const buildFormNode = (
55
- documentBlueprint: DocumentBlueprint,
56
- res: FormifiedDocumentNode,
57
- location: number[]
58
- ): FormNode => {
59
- const r = {
60
- documentBlueprintId: documentBlueprint.id,
61
- documentFormId: res._internalSys.path,
62
- values: res._values,
63
- internalSys: res._internalSys,
64
- location,
65
- }
66
- return r
67
- }