@tinacms/app 1.2.7 → 1.2.8

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,701 +0,0 @@
1
- /**
2
-
3
- */
4
- import { assign, createMachine, spawn } from 'xstate'
5
- import {
6
- Form,
7
- TinaCMS,
8
- NAMER,
9
- TinaFieldEnriched,
10
- TinaCollection,
11
- TinaSchema,
12
- GlobalFormPlugin,
13
- Client,
14
- } from 'tinacms'
15
- import * as G from 'graphql'
16
- import { formify } from '../formify'
17
- import { Data, documentMachine } from './document-machine'
18
- import type { ActorRefFrom } from 'xstate'
19
- import { getIn } from 'final-form'
20
-
21
- export type DataType = Record<string, unknown>
22
- type DocumentInfo = {
23
- ref: ActorRefFrom<typeof documentMachine>
24
- }
25
- type DocumentMap = {
26
- [documentId: string]: DocumentInfo & {
27
- /** We don't support nested forms or forms for list queries */
28
- skipFormRegister: boolean
29
- }
30
- }
31
-
32
- type ContextType = {
33
- id: null | string
34
- data: null | DataType
35
- cms: TinaCMS
36
- documentNode: G.DocumentNode
37
- variables: object
38
- iframe: null | HTMLIFrameElement
39
- registerSubForms?: boolean
40
- formifyCallback: (args: any) => Form
41
- documentMap: DocumentMap
42
- documents: Data[]
43
- }
44
- export const initialContext: Omit<ContextType, 'cms' | 'formifyCallback'> = {
45
- id: null,
46
- data: null,
47
- variables: {},
48
- documentMap: {},
49
- documents: [],
50
- documentNode: { kind: 'Document', definitions: [] },
51
- iframe: null,
52
- }
53
- export const queryMachine =
54
- /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgFcAnAGxNwirAGIBlAUQBUB9AVQCUAZRKAAOAe1i4ALrlH4hIAB6IAjAAYALCXXqAnACZVAVmUAOAGxmAzMp1mANCACeidXpMkdnnSZvLDh1X11AF9ghzQsPEJSSho6BhYOTgBJADkABW4uPkEkEDEJaVl5JQQAdh0SMtVqvTqassN1M0MHZwRLdWUPLx8dPwCg0PCMHAJicmpaeiY2LgARZOZ0-gBBAE0eAXkCqRk5PNKyvRJVM-OL8+U2xD0mns8+gcC9ELCQCLHoybiZxm50vNVuxWFtciJxHtiodECZKqoLOpVMpjiYTGUyp0bghlGYyg9vL5-C83iNIuNSMJcMIwFRxtMEqt5vNOABFbisXjrHaQooHUBHPSWEiGSw6VSWUWYvRWHTYsro07nSzIpE+CrDD6jKITKk0umEBmzbgAIXmAHkAMLcACyrFS7GYPMK+xKiGOwtF4sllmlsuxOlcIquJlUJm04pMms+Osp1Np9PiTGSADFeKs7ZwbebuA7WPNnVD+YpEKKzCKFWG0dZkSZWk5YRKlWdDMc8f5SVryd89QnDQQ9ug6QAvAhQRgQWRgWj4ABuogA1tOYxSSL2DdOB9Ih7hR-goAgCPPMOg+QBtVQAXULfLdOLU+Neej8yksNdM9faCs0l3FrleorRtqq7rom+CDiOY6MGAFAUKIFBrlQp4AGbwagJArj28YbjOEG7mOh5zqIJ7nleN6ujC941CQT4vm+agftiZg6Piv4aHU6iAe8mG6th9IAO7oHs+6MBa1p2g6nC8KwTLcnkuy3pRygPjRrh0e+dbYspujNmcf4cU0QHdrx+r0jS+AQFBk79kRS4YcBWGmYa5mWfuhHHqe+wXte8m8hRAoqKYmiSn4Mr0bWn4qJYipeJ4gSto0zRGV8Jl9tOLlQTBcEIcISGSKhFDoTxcZOelYAWQRR7EZ5sjeeR0IBVRj5qcY4WMQ2OIIicbH-pxnbFWufGGhQYDoBAjj-ICwKgjk9XFqUlh4iQnS+kiOgJU09gdfo5YXCqyhqiiOjJbGg2lSQI1jRN0nZgAaqC7KcnJEIug1JYdEtK1lGtG3NNi6LuBc6gmHUophmUJ0gUN06XeNiT8KwloLFatr2uwc13sphgijYooJcpdRmCYWnKeWsXiutGKbZDjlpRdo1wymySsPwLKWgAEqsqQAOKsBjlGLfiX0-VTf0dcoMruIYoTvPgogQHA8gDbERr841ynuDK63WHclyWNihgGASTzEvoNMTLEavvcDumXBcB0Btj5PxaLZh6ObJV00mVulE02Nu7oROSs+XR6P9626a2MqNIZ3EOalOFbrgO57lAPuIH+VQ+M+0UaZF5SGLt5xR+2sdkilns4YJwlp75r3zRn3inDovoaAq6LSiTb4kCYb4qvoBh4sdcfGZXZnla5tcvUWmO+t0am99WDGaeL1juAqraGCDRg2O7I8V2ddOw+00+KY1GJaQY7jO5TiVmB7h84Vl8Hpzic9aM+i+98v+dlGoBIU1+vffep1QKEFfqKJidxdKmDDBGBEHtX6vFtnbM4DsOpCgAeTXowDQhAA */
55
- createMachine(
56
- {
57
- tsTypes: {} as import('./query-machine.typegen').Typegen0,
58
- schema: {
59
- context: {} as ContextType,
60
- services: {} as {
61
- initializer: {
62
- data: {
63
- data: DataType
64
- }
65
- }
66
- setter: {
67
- data: { data: DataType }
68
- }
69
- subDocumentResolver: {
70
- data: {
71
- id: string
72
- location: string
73
- }[]
74
- }
75
- onChangeCallback: {
76
- data: undefined
77
- }
78
- },
79
- events: {} as
80
- | {
81
- type: 'IFRAME_MOUNTED'
82
- value: HTMLIFrameElement
83
- }
84
- | {
85
- type: 'SELECT_DOCUMENT'
86
- value: string
87
- }
88
- | {
89
- type: 'DOCUMENT_READY'
90
- value: string
91
- }
92
- | {
93
- type: 'NAVIGATE'
94
- }
95
- | {
96
- type: 'ADD_QUERY'
97
- value: {
98
- id: string
99
- type: 'open' | 'close'
100
- query: string
101
- data: object
102
- variables: object
103
- }
104
- }
105
- | {
106
- type: 'REMOVE_QUERY'
107
- value: string
108
- }
109
- | {
110
- type: 'SUBDOCUMENTS'
111
- value: { id: string; location: string }[]
112
- }
113
- | {
114
- type: 'FIELD_CHANGE'
115
- }
116
- | {
117
- type: 'EDIT_MODE'
118
- },
119
- },
120
- id: '(machine)',
121
- type: 'parallel',
122
- states: {
123
- pipeline: {
124
- initial: 'idle',
125
- states: {
126
- idle: {
127
- entry: 'clear',
128
- on: {
129
- ADD_QUERY: {
130
- target: 'initializing',
131
- },
132
- SUBDOCUMENTS: {
133
- target: 'pending',
134
- },
135
- IFRAME_MOUNTED: {
136
- actions: 'setIframe',
137
- },
138
- },
139
- },
140
- initializing: {
141
- invoke: {
142
- src: 'initializer',
143
- onDone: [
144
- {
145
- actions: 'storeInitialValues',
146
- target: 'pending',
147
- },
148
- ],
149
- onError: [
150
- {
151
- actions: 'handleError',
152
- target: 'error',
153
- },
154
- ],
155
- },
156
- },
157
- waiting: {
158
- on: {
159
- DOCUMENT_READY: {
160
- target: 'pending',
161
- },
162
- },
163
- },
164
- pending: {
165
- invoke: {
166
- src: 'setter',
167
- onDone: [
168
- {
169
- target: 'ready',
170
- },
171
- ],
172
- onError: [
173
- {
174
- actions: 'handleMissingDocument',
175
- target: 'waiting',
176
- },
177
- ],
178
- },
179
- },
180
- ready: {
181
- entry: 'resolveData',
182
- invoke: {
183
- src: 'onChangeCallback',
184
- },
185
- on: {
186
- NAVIGATE: {
187
- target: 'idle',
188
- },
189
- REMOVE_QUERY: {
190
- target: 'idle',
191
- },
192
- FIELD_CHANGE: {
193
- target: 'pending',
194
- },
195
- },
196
- },
197
- error: {},
198
- },
199
- },
200
- },
201
- },
202
- {
203
- actions: {
204
- handleError: (_context, event) => console.error(event.data),
205
- handleMissingDocument: assign((context, event) => {
206
- if (event.data instanceof QueryError) {
207
- if (context.documentMap[event.data.id]) {
208
- // Already exists
209
- return context
210
- }
211
- if (!event.data.id) {
212
- return context
213
- }
214
- const existingData = context.documents.find(
215
- (doc) => doc._internalSys.path === event.data.id
216
- )
217
- const doc = {
218
- ref: spawn(
219
- documentMachine.withContext({
220
- id: event.data.id,
221
- cms: context.cms,
222
- formifyCallback: context.formifyCallback,
223
- form: null,
224
- data: existingData || null,
225
- })
226
- ),
227
- }
228
-
229
- return {
230
- ...context,
231
- documentMap: {
232
- ...context.documentMap,
233
- [event.data.id]: {
234
- ...doc,
235
- skipFormRegister: event.data.skipFormRegister,
236
- },
237
- },
238
- }
239
- } else {
240
- console.error(event.data)
241
- return context
242
- }
243
- }),
244
- clear: assign((context) => {
245
- context.cms.forms.all().forEach((form) => {
246
- context.cms.forms.remove(form.id)
247
- })
248
- return {
249
- ...initialContext,
250
- formifyCallback: context.formifyCallback,
251
- cms: context.cms,
252
- // documentMap: context.documentMap, // to preserve docs across pages
253
- iframe: context.iframe,
254
- }
255
- }),
256
- storeInitialValues: assign((context, event) => {
257
- return {
258
- ...context,
259
- ...event.data,
260
- }
261
- }),
262
- setIframe: assign((context, event) => {
263
- return {
264
- ...context,
265
- iframe: event.value,
266
- }
267
- }),
268
- resolveData: assign((context, event) => {
269
- if (context.iframe) {
270
- context.iframe?.contentWindow?.postMessage({
271
- type: 'updateData',
272
- id: context.id,
273
- data: event.data.data,
274
- })
275
- }
276
- return {
277
- ...context,
278
- data: event.data.data,
279
- }
280
- }),
281
- },
282
- services: {
283
- setter: async (context) => {
284
- const tinaSchema = context.cms.api.tina.schema as TinaSchema
285
- const gqlSchema = context.cms.api.tina.gqlSchema
286
- const missingForms: { id: string; skipFormRegister: boolean }[] = []
287
- const newData = await G.graphql({
288
- schema: gqlSchema,
289
- source: G.print(context.documentNode),
290
- rootValue: context.data,
291
- variableValues: context.variables,
292
- fieldResolver: (source, args, _context, info) => {
293
- const fieldName = info.fieldName
294
-
295
- /**
296
- * Since the `source` for this resolver is the query that
297
- * ran before passing it into `useTina`, we need to take aliases
298
- * into consideration, so if an alias is provided we try to
299
- * see if that has the value we're looking for. This isn't a perfect
300
- * solution as the `value` gets overwritten depending on the alias
301
- * query.
302
- */
303
- const aliases: string[] = []
304
- info.fieldNodes.forEach((fieldNode) => {
305
- if (fieldNode.alias) {
306
- aliases.push(fieldNode.alias.value)
307
- }
308
- })
309
- let value = source[fieldName]
310
- if (!value) {
311
- aliases.forEach((alias) => {
312
- const aliasValue = source[alias]
313
- if (aliasValue) {
314
- value = aliasValue
315
- }
316
- })
317
- }
318
- /**
319
- * Formify adds `_internalSys` and `_internalValues` to the query
320
- * and a user's query might also include `_values` or `_sys`, but
321
- * it may not contain all of the info we need, so the actual
322
- * source of truth for these values is our alias ones, which are
323
- * also guaranteed to include all of the values another `_sys` query
324
- * might include
325
- */
326
- if (fieldName === '_sys') {
327
- return source._internalSys
328
- }
329
- if (fieldName === '_values') {
330
- return source._internalValues
331
- }
332
- if (isNodeType(info.returnType)) {
333
- const existingValue = source[fieldName]
334
- let skipFormRegister = false
335
- if (!existingValue) {
336
- return null
337
- }
338
- let path: string = ''
339
- if (typeof existingValue === 'string') {
340
- // this is a reference value (eg. post.author)
341
- skipFormRegister = true
342
- path = existingValue
343
- } else {
344
- path = existingValue._internalSys.path
345
- }
346
- if (context.documentMap[path]) {
347
- const documentMachine = context.documentMap[path].ref
348
- const documentContext = documentMachine.getSnapshot()?.context
349
- if (!documentContext) {
350
- throw new Error(
351
- `Document not set up properly for id: ${path}`
352
- )
353
- }
354
- const { data, form } = documentContext
355
- const values = form?.values
356
- if (!data || !form || !values) {
357
- throw new Error(
358
- `Document not set up properly for id: ${path}`
359
- )
360
- }
361
- const collectionName = data._internalSys.collection.name
362
- const extraValues = documentContext.data
363
- const formVal = resolveFormValue({
364
- fields: form.fields,
365
- values: values,
366
- tinaSchema,
367
- })
368
- const template = tinaSchema.getTemplateForData({
369
- data: form.values,
370
- collection: tinaSchema.getCollection(collectionName),
371
- })
372
- return {
373
- ...extraValues,
374
- ...formVal,
375
- _sys: data._internalSys,
376
- id: path,
377
- __typename: NAMER.dataTypeName(template.namespace),
378
- }
379
- } else {
380
- // TODO: when we support forms in lists, remove this check
381
- // This checks that we're at least 2 levels deep, meaning top-level
382
- // queries list page(relativePath: '...') will be registered, but
383
- // not connection nodes like pageConnection.edges.node
384
- if (info.path?.prev?.prev) {
385
- skipFormRegister = true
386
- }
387
- missingForms.push({ id: path, skipFormRegister })
388
- return null
389
- }
390
- }
391
- return value
392
- },
393
- })
394
- if (missingForms.length > 0) {
395
- // Only run this one at a time
396
- const missingForm = missingForms[0]
397
- throw new QueryError(
398
- `Unable to resolve form for initial document`,
399
- missingForm.id,
400
- missingForm.skipFormRegister
401
- )
402
- }
403
- // Populate __meta__ property for use
404
- // in active field indicator utility
405
- const META_KEY = '__meta__'
406
- function* traverse(o) {
407
- const memory = new Set()
408
- function* innerTraversal(o, path = []) {
409
- if (memory.has(o)) {
410
- // we've seen this object before don't iterate it
411
- return
412
- }
413
- // add the new object to our memory.
414
- memory.add(o)
415
- for (let i of Object.keys(o)) {
416
- const itemPath = path.concat(i)
417
- yield [i, o[i], itemPath, o]
418
- if (o[i] !== null && typeof o[i] == 'object') {
419
- if (
420
- [
421
- '_internalSys',
422
- '_internalValues',
423
- '_sys',
424
- META_KEY,
425
- ].includes(i)
426
- ) {
427
- //going one step down in the object tree!!
428
- yield* innerTraversal(o[i], itemPath)
429
- }
430
- }
431
- }
432
- }
433
- yield* innerTraversal(o)
434
- }
435
- const nodePaths = []
436
- for (let [key, value, path, parent] of traverse(newData.data)) {
437
- if (value?._internalSys) {
438
- nodePaths.push(path)
439
- const fields = {}
440
- const parents = nodePaths.filter((nodePath) => {
441
- return path.join('.').startsWith(nodePath.join('.'))
442
- })
443
-
444
- const nearestParent = parents.reduce(function (a, b) {
445
- return a.length < b.length ? a : b
446
- })
447
- Object.keys(value).map((key) => {
448
- if (
449
- [
450
- '__typename',
451
- '_internalSys',
452
- '_internalValues',
453
- '_sys',
454
- ].includes(key)
455
- ) {
456
- return false
457
- }
458
- fields[key] = [
459
- ...path.slice(nearestParent.length + 1),
460
- key,
461
- ].join('.')
462
- })
463
- value[META_KEY] = {
464
- id: value?._internalSys.path,
465
- name: path.slice(nearestParent.length).join('.'),
466
- fields,
467
- }
468
- } else if (
469
- typeof value === 'object' &&
470
- !Array.isArray(value) &&
471
- value !== null
472
- ) {
473
- if (key !== META_KEY) {
474
- const parents = nodePaths.filter((nodePath) => {
475
- return path.join('.').startsWith(nodePath.join('.'))
476
- })
477
- if (parents.length) {
478
- const nearestParent = parents.reduce(function (a, b) {
479
- return a.length < b.length ? a : b
480
- })
481
- const parent = getIn(newData.data, nearestParent.join('.'))
482
- const id = parent._internalSys.path
483
- const fields = {}
484
- Object.keys(value).map((key) => {
485
- if (
486
- [
487
- '__typename',
488
- '_internalSys',
489
- '_internalValues',
490
- '_sys',
491
- ].includes(key)
492
- ) {
493
- return false
494
- }
495
-
496
- fields[key] = [
497
- ...path.slice(nearestParent.length),
498
- key,
499
- ].join('.')
500
- })
501
- value[META_KEY] = {
502
- id,
503
- name: path.slice(nearestParent.length).join('.'),
504
- fields,
505
- }
506
- }
507
- }
508
- }
509
- }
510
- return { data: newData.data }
511
- },
512
- initializer: async (context, event) => {
513
- const tina = context.cms.api.tina as Client
514
- const schema = await tina.getSchema()
515
- const documentNode = G.parse(event.value.query)
516
- const { formifiedQuery } = await formify({
517
- schema,
518
- optimizedDocumentNode: documentNode,
519
- })
520
- const data = (await context.cms.api.tina.request(
521
- G.print(formifiedQuery),
522
- {
523
- variables: event.value.variables,
524
- }
525
- )) as DataType
526
- const documents: Data[] = []
527
- // step through every value in the payload to find the documents
528
- JSON.stringify(data, (key, value) => {
529
- if (value?._internalValues) {
530
- documents.push(value)
531
- }
532
- return value
533
- })
534
- return {
535
- data,
536
- documents,
537
- variables: event.value.variables,
538
- documentNode: formifiedQuery,
539
- id: event.value.id,
540
- }
541
- },
542
- onChangeCallback: (context) => (callback, _onReceive) => {
543
- const schema = context.cms.api.tina.schema as TinaSchema
544
- Object.values(context.documentMap).forEach((documentMachine) => {
545
- if (!context.registerSubForms) {
546
- if (documentMachine.skipFormRegister) {
547
- return
548
- }
549
- }
550
- const documentContext = documentMachine.ref.getSnapshot()?.context
551
- const collectionName =
552
- documentContext?.data?._internalSys.collection.name
553
-
554
- const collection = schema.getCollection(
555
- collectionName || ''
556
- ) as TinaCollection
557
- if (documentContext?.form) {
558
- if (collection.ui?.global) {
559
- context.cms.plugins.add(
560
- new GlobalFormPlugin(documentContext.form)
561
- )
562
- } else {
563
- context.cms.forms.add(documentContext.form)
564
- }
565
- }
566
- })
567
- },
568
- },
569
- }
570
- )
571
- class QueryError extends Error {
572
- public id: string
573
- public skipFormRegister: boolean
574
- constructor(message: string, id: string, skipFormRegister: boolean) {
575
- super(message) // (1)
576
- this.name = 'QueryError' // (2)
577
- this.id = id
578
- this.skipFormRegister = skipFormRegister
579
- }
580
- }
581
-
582
- const isNodeType = (type: G.GraphQLOutputType) => {
583
- const namedType = G.getNamedType(type)
584
- if (G.isInterfaceType(namedType)) {
585
- if (namedType.name === 'Node') {
586
- return true
587
- }
588
- }
589
- if (G.isUnionType(namedType)) {
590
- const types = namedType.getTypes()
591
- if (
592
- types.every((type) => {
593
- return type.getInterfaces().some((intfc) => intfc.name === 'Node')
594
- })
595
- ) {
596
- return true
597
- }
598
- }
599
- if (G.isObjectType(namedType)) {
600
- if (namedType.getInterfaces().some((intfc) => intfc.name === 'Node')) {
601
- return true
602
- }
603
- }
604
- }
605
-
606
- const resolveFormValue = <T extends Record<string, unknown>>({
607
- fields,
608
- values,
609
- tinaSchema,
610
- }: {
611
- fields: TinaFieldEnriched[]
612
- values: T
613
- tinaSchema: TinaSchema
614
- }): T & { __typename?: string } => {
615
- const accum: Record<string, unknown> = {}
616
- fields.forEach((field) => {
617
- const v = values[field.name]
618
- if (typeof v === 'undefined') {
619
- return
620
- }
621
- if (v === null) {
622
- return
623
- }
624
- accum[field.name] = resolveFieldValue({
625
- field,
626
- value: v,
627
- tinaSchema,
628
- })
629
- })
630
- return accum as T & { __typename?: string }
631
- }
632
- const resolveFieldValue = ({
633
- field,
634
- value,
635
- tinaSchema,
636
- }: {
637
- field: TinaFieldEnriched
638
- value: unknown
639
- tinaSchema: TinaSchema
640
- }) => {
641
- switch (field.type) {
642
- case 'object': {
643
- if (field.templates) {
644
- if (field.list) {
645
- if (Array.isArray(value)) {
646
- return value.map((item) => {
647
- const template = field.templates[item._template]
648
- if (typeof template === 'string') {
649
- throw new Error('Global templates not supported')
650
- }
651
- return {
652
- __typename: NAMER.dataTypeName(template.namespace),
653
- ...resolveFormValue({
654
- fields: template.fields,
655
- values: item,
656
- tinaSchema,
657
- }),
658
- }
659
- })
660
- }
661
- } else {
662
- // not implemented
663
- }
664
- }
665
-
666
- const templateFields = field.fields
667
- if (typeof templateFields === 'string') {
668
- throw new Error('Global templates not supported')
669
- }
670
- if (!templateFields) {
671
- throw new Error(`Expected to find sub-fields on field ${field.name}`)
672
- }
673
- if (field.list) {
674
- if (Array.isArray(value)) {
675
- return value.map((item) => {
676
- return {
677
- __typename: NAMER.dataTypeName(field.namespace),
678
- ...resolveFormValue({
679
- fields: templateFields,
680
- values: item,
681
- tinaSchema,
682
- }),
683
- }
684
- })
685
- }
686
- } else {
687
- return {
688
- __typename: NAMER.dataTypeName(field.namespace),
689
- ...resolveFormValue({
690
- fields: templateFields,
691
- values: value as any,
692
- tinaSchema,
693
- }),
694
- }
695
- }
696
- }
697
- default: {
698
- return value
699
- }
700
- }
701
- }