@financial-times/cp-content-pipeline-schema 2.15.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/lib/datasources/capi.d.ts +2 -2
  3. package/lib/datasources/capi.js +4 -2
  4. package/lib/datasources/capi.js.map +1 -1
  5. package/lib/datasources/capi.test.js +2 -2
  6. package/lib/fixtures/capiObject.d.ts +2 -2
  7. package/lib/fixtures/capiObject.js +2 -0
  8. package/lib/fixtures/capiObject.js.map +1 -1
  9. package/lib/fixtures/capiPerson.d.ts +1 -1
  10. package/lib/generated/index.d.ts +280 -115
  11. package/lib/helpers/decorateHeadshotUrl.d.ts +1 -2
  12. package/lib/helpers/decorateHeadshotUrl.js +2 -3
  13. package/lib/helpers/decorateHeadshotUrl.js.map +1 -1
  14. package/lib/model/Byline.d.ts +8 -10
  15. package/lib/model/Byline.js +34 -33
  16. package/lib/model/Byline.js.map +1 -1
  17. package/lib/model/Byline.test.js +105 -52
  18. package/lib/model/Byline.test.js.map +1 -1
  19. package/lib/model/CapiResponse.d.ts +11 -15
  20. package/lib/model/CapiResponse.js +37 -40
  21. package/lib/model/CapiResponse.js.map +1 -1
  22. package/lib/model/CapiResponse.test.js +7 -18
  23. package/lib/model/CapiResponse.test.js.map +1 -1
  24. package/lib/model/Clip.d.ts +1 -1
  25. package/lib/model/Concept.d.ts +1 -1
  26. package/lib/model/Concept.js +1 -2
  27. package/lib/model/Concept.js.map +1 -1
  28. package/lib/model/FlourishSource.d.ts +1 -1
  29. package/lib/model/FlourishSource.js.map +1 -1
  30. package/lib/model/Image.d.ts +1 -1
  31. package/lib/model/LeadFlourish.test.js +1 -0
  32. package/lib/model/LeadFlourish.test.js.map +1 -1
  33. package/lib/model/Person.d.ts +6 -12
  34. package/lib/model/Person.js +39 -66
  35. package/lib/model/Person.js.map +1 -1
  36. package/lib/model/Person.test.js +7 -60
  37. package/lib/model/Person.test.js.map +1 -1
  38. package/lib/model/Picture.d.ts +1 -1
  39. package/lib/model/RichText.d.ts +1 -2
  40. package/lib/model/Topper.d.ts +1 -1
  41. package/lib/model/Topper.js +10 -8
  42. package/lib/model/Topper.js.map +1 -1
  43. package/lib/model/Topper.test.js +9 -10
  44. package/lib/model/Topper.test.js.map +1 -1
  45. package/lib/model/schemas/capi/article.d.ts +4 -1
  46. package/lib/model/schemas/capi/article.js +1 -0
  47. package/lib/model/schemas/capi/article.js.map +1 -1
  48. package/lib/model/schemas/capi/audio.d.ts +4 -1
  49. package/lib/model/schemas/capi/audio.js +1 -0
  50. package/lib/model/schemas/capi/audio.js.map +1 -1
  51. package/lib/model/schemas/capi/base-schema.d.ts +11 -120
  52. package/lib/model/schemas/capi/base-schema.js +6 -5
  53. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  54. package/lib/model/schemas/capi/content-package.d.ts +7 -2
  55. package/lib/model/schemas/capi/content-package.js +2 -0
  56. package/lib/model/schemas/capi/content-package.js.map +1 -1
  57. package/lib/model/schemas/capi/index.d.ts +26 -7
  58. package/lib/model/schemas/capi/internal-content.d.ts +24 -0
  59. package/lib/model/schemas/capi/internal-content.js +3 -0
  60. package/lib/model/schemas/capi/internal-content.js.map +1 -0
  61. package/lib/model/schemas/capi/live-blog-package.d.ts +4 -1
  62. package/lib/model/schemas/capi/live-blog-package.js +1 -0
  63. package/lib/model/schemas/capi/live-blog-package.js.map +1 -1
  64. package/lib/model/schemas/capi/placeholder.d.ts +4 -1
  65. package/lib/model/schemas/capi/placeholder.js +1 -0
  66. package/lib/model/schemas/capi/placeholder.js.map +1 -1
  67. package/lib/model/schemas/capi/video.d.ts +7 -2
  68. package/lib/model/schemas/capi/video.js +2 -0
  69. package/lib/model/schemas/capi/video.js.map +1 -1
  70. package/lib/resolvers/concept.d.ts +37 -2
  71. package/lib/resolvers/concept.js +17 -10
  72. package/lib/resolvers/concept.js.map +1 -1
  73. package/lib/resolvers/content-tree/Workarounds.d.ts +19 -11
  74. package/lib/resolvers/content-tree/references/Author.d.ts +4 -0
  75. package/lib/resolvers/content-tree/references/Author.js +14 -0
  76. package/lib/resolvers/content-tree/references/Author.js.map +1 -0
  77. package/lib/resolvers/content-tree/references/ClipSet.d.ts +1 -1
  78. package/lib/resolvers/content-tree/references/ClipSet.js +1 -1
  79. package/lib/resolvers/content-tree/references/ClipSet.js.map +1 -1
  80. package/lib/resolvers/content-tree/references/Flourish.d.ts +1 -1
  81. package/lib/resolvers/content-tree/references/Reference.d.ts +1 -1
  82. package/lib/resolvers/content-tree/references/index.d.ts +4 -2
  83. package/lib/resolvers/content-tree/references/index.js +4 -1
  84. package/lib/resolvers/content-tree/references/index.js.map +1 -1
  85. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.d.ts +3 -3
  86. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js +2 -3
  87. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js.map +1 -1
  88. package/lib/resolvers/content.d.ts +19 -10
  89. package/lib/resolvers/content.js +4 -2
  90. package/lib/resolvers/content.js.map +1 -1
  91. package/lib/resolvers/image.d.ts +12 -12
  92. package/lib/resolvers/index.d.ts +81 -34
  93. package/lib/resolvers/leadFlourish.d.ts +2 -1
  94. package/lib/resolvers/leadFlourish.js +1 -0
  95. package/lib/resolvers/leadFlourish.js.map +1 -1
  96. package/lib/resolvers/person.d.ts +1 -1
  97. package/lib/resolvers/person.js +1 -1
  98. package/lib/resolvers/person.js.map +1 -1
  99. package/lib/resolvers/picture.d.ts +4 -4
  100. package/lib/resolvers/richText.d.ts +1 -1
  101. package/lib/resolvers/teaser.d.ts +1 -1
  102. package/lib/resolvers/topper.d.ts +3 -3
  103. package/package.json +1 -1
  104. package/queries/article.graphql +35 -13
  105. package/src/datasources/capi.test.ts +3 -3
  106. package/src/datasources/capi.ts +5 -3
  107. package/src/fixtures/capiObject.ts +4 -2
  108. package/src/fixtures/capiPerson.ts +1 -1
  109. package/src/generated/index.ts +305 -116
  110. package/src/helpers/decorateHeadshotUrl.ts +2 -2
  111. package/src/model/Byline.test.ts +136 -55
  112. package/src/model/Byline.ts +49 -39
  113. package/src/model/CapiResponse.test.ts +9 -25
  114. package/src/model/CapiResponse.ts +82 -55
  115. package/src/model/Clip.ts +1 -1
  116. package/src/model/Concept.ts +3 -3
  117. package/src/model/FlourishSource.ts +1 -1
  118. package/src/model/Image.test.ts +1 -1
  119. package/src/model/Image.ts +1 -1
  120. package/src/model/LeadFlourish.test.ts +1 -0
  121. package/src/model/Person.test.ts +11 -62
  122. package/src/model/Person.ts +47 -51
  123. package/src/model/Picture.test.ts +1 -1
  124. package/src/model/Picture.ts +1 -1
  125. package/src/model/Topper.test.ts +22 -18
  126. package/src/model/Topper.ts +10 -9
  127. package/src/model/__snapshots__/Byline.test.ts.snap +166 -27
  128. package/src/model/schemas/capi/article.ts +1 -0
  129. package/src/model/schemas/capi/audio.ts +1 -0
  130. package/src/model/schemas/capi/base-schema.ts +4 -3
  131. package/src/model/schemas/capi/content-package.ts +2 -0
  132. package/src/model/schemas/capi/internal-content.ts +45 -0
  133. package/src/model/schemas/capi/live-blog-package.ts +1 -0
  134. package/src/model/schemas/capi/placeholder.ts +1 -0
  135. package/src/model/schemas/capi/video.ts +2 -0
  136. package/src/resolvers/concept.ts +29 -12
  137. package/src/resolvers/content-tree/Workarounds.ts +39 -20
  138. package/src/resolvers/content-tree/references/Author.ts +18 -0
  139. package/src/resolvers/content-tree/references/ClipSet.ts +6 -8
  140. package/src/resolvers/content-tree/references/ImageSet.ts +1 -1
  141. package/src/resolvers/content-tree/references/ScrollyImage.ts +1 -1
  142. package/src/resolvers/content-tree/references/index.ts +6 -1
  143. package/src/resolvers/content-tree/updateTreeWithReferenceIds.ts +6 -8
  144. package/src/resolvers/content.ts +4 -2
  145. package/src/resolvers/leadFlourish.ts +1 -0
  146. package/src/resolvers/person.ts +1 -1
  147. package/src/types/n-display-metadata.d.ts +1 -1
  148. package/tsconfig.tsbuildinfo +1 -1
  149. package/typedefs/clip.graphql +2 -2
  150. package/typedefs/concept.graphql +64 -2
  151. package/typedefs/content.graphql +55 -31
  152. package/typedefs/image.graphql +12 -12
  153. package/typedefs/leadFlourish.graphql +32 -0
  154. package/typedefs/person.graphql +2 -2
  155. package/typedefs/picture.graphql +6 -6
  156. package/typedefs/references/author.graphql +7 -0
  157. package/typedefs/references/clipSet.graphql +14 -2
  158. package/typedefs/references/tweet.graphql +1 -1
  159. package/typedefs/teaser.graphql +10 -10
  160. package/src/types/internal-content.d.ts +0 -55
  161. package/typedefs/leadFlouish.graphql +0 -29
@@ -2,13 +2,17 @@ import { Topper } from './Topper'
2
2
  import { Person } from './Person'
3
3
  import { baseCapiObject } from '../fixtures/capiObject'
4
4
  import { CapiResponse } from './CapiResponse'
5
- import { Concept, predicates } from './Concept'
5
+ import { predicates } from './Concept'
6
6
  import cloneDeep from 'clone-deep'
7
7
  import conceptIds from '@financial-times/n-concept-ids'
8
8
 
9
9
  import type { QueryContext } from '..'
10
- import type { Image as CAPIImage } from '../types/internal-content'
10
+ import type {
11
+ Article,
12
+ Image as CAPIImage,
13
+ } from './schemas/capi/internal-content'
11
14
  import imageServiceUrl from '../helpers/imageService'
15
+ import { capiPerson } from '../fixtures/capiPerson'
12
16
 
13
17
  const context = {} as unknown as QueryContext
14
18
  describe('produces the correct types', () => {
@@ -154,13 +158,14 @@ describe('produces the correct types', () => {
154
158
  it('flourish topper', () => {
155
159
  const clonedBase = cloneDeep(baseCapiObject)
156
160
  clonedBase.leadFlourish = {
157
- id: 'test-id',
158
- description: 'test-description',
159
- type: 'test-type'
160
- }
161
+ id: 'test-id',
162
+ description: 'test-description',
163
+ type: 'test-type',
164
+ }
161
165
  clonedBase.topper = {
162
- layout: 'flourish',
163
- layoutWidth: 'full-width'
166
+ layout: 'flourish',
167
+ layoutWidth: 'full-width',
168
+ backgroundColour: 'paper',
164
169
  }
165
170
  const capiResponse = new CapiResponse(clonedBase, context)
166
171
  const topper = new Topper(capiResponse, context)
@@ -235,7 +240,8 @@ describe('it retrieves the correct headline', () => {
235
240
 
236
241
  describe('produces the correct intro', () => {
237
242
  it('uses summary if it exists', () => {
238
- const clonedBase = cloneDeep(baseCapiObject)
243
+ const clonedBase: Article & { summary?: typeof summary } =
244
+ cloneDeep(baseCapiObject)
239
245
  const summary = {
240
246
  bodyXML: 'some form of xml for the document summary',
241
247
  }
@@ -468,24 +474,22 @@ describe('headshot method', () => {
468
474
  jest.clearAllMocks()
469
475
  })
470
476
 
471
- it('returns null if the content is not opinion genre', () => {
477
+ it('returns null if the content is not opinion genre', async () => {
472
478
  jest.spyOn(capiResponse, 'isPodcast').mockReturnValue(false)
473
479
  jest.spyOn(capiResponse, 'isOpinion').mockReturnValue(false)
474
480
 
475
- const result = topper.headshot({})
476
-
477
- expect(result).toBeNull()
481
+ await expect(topper.headshot({})).resolves.toBeNull()
478
482
  })
479
483
 
480
484
  it('attempts to return the author headshot if the content is opinion genre', async () => {
481
485
  jest.spyOn(capiResponse, 'isPodcast').mockReturnValue(false)
482
486
  jest.spyOn(capiResponse, 'isOpinion').mockReturnValue(true)
483
- jest.spyOn(capiResponse, 'getAuthors').mockReturnValue([{} as Concept])
484
487
  jest
485
- .spyOn(Person.prototype, 'headshot')
486
- .mockReturnValue(Promise.resolve(null))
488
+ .spyOn(capiResponse, 'primaryAuthor')
489
+ .mockResolvedValue(new Person(capiPerson, context))
487
490
 
488
- await topper.headshot({})
489
- expect(Person.prototype.headshot).toHaveBeenCalledTimes(1)
491
+ expect(await topper.headshot({})).toBe(
492
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fd1e00ek4ebabms.cloudfront.net%2Fproduction%2Fuploaded-files%2Ffthead-v1_robert-shrimsley-cc467908-15d6-474d-94d8-171594ceabb9.png?source=cp-content-pipeline&fit=scale-down&quality=highest&width=150&dpr=1'
493
+ )
490
494
  })
491
495
  })
@@ -10,6 +10,7 @@ import { RichText } from './RichText'
10
10
  import imageServiceUrl from '../helpers/imageService'
11
11
  import { LeadFlourish } from './LeadFlourish'
12
12
  import type { TopperWithHeadshotHeadshotArgs } from '../generated'
13
+ import { predicates } from './Concept'
13
14
 
14
15
  type TopperType =
15
16
  | 'DeepPortraitTopper'
@@ -295,7 +296,9 @@ export class Topper {
295
296
  }
296
297
 
297
298
  columnist() {
298
- const authors = this.capiResponse.getAuthors()
299
+ const authors = this.capiResponse.annotations({
300
+ byPredicate: predicates.hasAuthor,
301
+ })
299
302
  const isOpinionOrColumn =
300
303
  this.type() === 'OpinionTopper' || this.capiResponse.isColumn()
301
304
 
@@ -334,18 +337,16 @@ export class Topper {
334
337
  return null
335
338
  }
336
339
 
337
- headshot(args: TopperWithHeadshotHeadshotArgs) {
340
+ async headshot(args: TopperWithHeadshotHeadshotArgs) {
338
341
  if (this.capiResponse.isPodcast()) {
339
- args.url = this.capiResponse.mainImage()?.url()
340
- return this.imageService(args)
342
+ return this.imageService({
343
+ ...args,
344
+ url: this.capiResponse.mainImage()?.url(),
345
+ })
341
346
  }
342
347
 
343
348
  if (this.capiResponse.isOpinion()) {
344
- const authors = this.capiResponse.authors()
345
-
346
- if (authors && authors[0]) {
347
- return authors[0].headshot(args)
348
- }
349
+ return (await this.capiResponse.primaryAuthor())?.headshot(args) ?? null
349
350
  }
350
351
 
351
352
  return null
@@ -2,7 +2,24 @@
2
2
 
3
3
  exports[`byline transformation adds a link around a single known author 1`] = `
4
4
  Object {
5
- "references": Array [],
5
+ "references": Array [
6
+ Object {
7
+ "contentApiData": undefined,
8
+ "reference": Object {
9
+ "children": Array [
10
+ Object {
11
+ "type": "text",
12
+ "value": "Chris Giles",
13
+ },
14
+ ],
15
+ "data": Object {
16
+ "referenceIndex": 0,
17
+ },
18
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
19
+ "type": "author",
20
+ },
21
+ },
22
+ ],
6
23
  "tree": Object {
7
24
  "children": Array [
8
25
  Object {
@@ -12,22 +29,58 @@ Object {
12
29
  "value": "Chris Giles",
13
30
  },
14
31
  ],
15
- "href": "https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28",
16
- "type": "author-link",
32
+ "data": Object {
33
+ "referenceIndex": 0,
34
+ },
35
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
36
+ "type": "author",
17
37
  },
18
38
  Object {
19
39
  "type": "text",
20
40
  "value": " in London",
21
41
  },
22
42
  ],
23
- "type": "root",
43
+ "type": "byline",
24
44
  },
25
45
  }
26
46
  `;
27
47
 
28
48
  exports[`byline transformation adds links around multiple known authors 1`] = `
29
49
  Object {
30
- "references": Array [],
50
+ "references": Array [
51
+ Object {
52
+ "contentApiData": undefined,
53
+ "reference": Object {
54
+ "children": Array [
55
+ Object {
56
+ "type": "text",
57
+ "value": "Chris Giles",
58
+ },
59
+ ],
60
+ "data": Object {
61
+ "referenceIndex": 0,
62
+ },
63
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
64
+ "type": "author",
65
+ },
66
+ },
67
+ Object {
68
+ "contentApiData": undefined,
69
+ "reference": Object {
70
+ "children": Array [
71
+ Object {
72
+ "type": "text",
73
+ "value": "Martin Wolf",
74
+ },
75
+ ],
76
+ "data": Object {
77
+ "referenceIndex": 1,
78
+ },
79
+ "id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
80
+ "type": "author",
81
+ },
82
+ },
83
+ ],
31
84
  "tree": Object {
32
85
  "children": Array [
33
86
  Object {
@@ -37,8 +90,11 @@ Object {
37
90
  "value": "Chris Giles",
38
91
  },
39
92
  ],
40
- "href": "https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28",
41
- "type": "author-link",
93
+ "data": Object {
94
+ "referenceIndex": 0,
95
+ },
96
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
97
+ "type": "author",
42
98
  },
43
99
  Object {
44
100
  "type": "text",
@@ -51,22 +107,42 @@ Object {
51
107
  "value": "Martin Wolf",
52
108
  },
53
109
  ],
54
- "href": "https://www.ft.com/stream/uuid/7c1e1e72-57ae-4461-862a-f8d24dd42e22",
55
- "type": "author-link",
110
+ "data": Object {
111
+ "referenceIndex": 1,
112
+ },
113
+ "id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
114
+ "type": "author",
56
115
  },
57
116
  Object {
58
117
  "type": "text",
59
118
  "value": " in London",
60
119
  },
61
120
  ],
62
- "type": "root",
121
+ "type": "byline",
63
122
  },
64
123
  }
65
124
  `;
66
125
 
67
126
  exports[`byline transformation converts straight apostrophes in byline 1`] = `
68
127
  Object {
69
- "references": Array [],
128
+ "references": Array [
129
+ Object {
130
+ "contentApiData": undefined,
131
+ "reference": Object {
132
+ "children": Array [
133
+ Object {
134
+ "type": "text",
135
+ "value": "Sarah O’Connor",
136
+ },
137
+ ],
138
+ "data": Object {
139
+ "referenceIndex": 0,
140
+ },
141
+ "id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
142
+ "type": "author",
143
+ },
144
+ },
145
+ ],
70
146
  "tree": Object {
71
147
  "children": Array [
72
148
  Object {
@@ -76,15 +152,18 @@ Object {
76
152
  "value": "Sarah O’Connor",
77
153
  },
78
154
  ],
79
- "href": "https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
80
- "type": "author-link",
155
+ "data": Object {
156
+ "referenceIndex": 0,
157
+ },
158
+ "id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
159
+ "type": "author",
81
160
  },
82
161
  Object {
83
162
  "type": "text",
84
163
  "value": " in Bali",
85
164
  },
86
165
  ],
87
- "type": "root",
166
+ "type": "byline",
88
167
  },
89
168
  }
90
169
  `;
@@ -99,14 +178,31 @@ Object {
99
178
  "value": "Chris Giles and Nick Ramsbottom in London",
100
179
  },
101
180
  ],
102
- "type": "root",
181
+ "type": "byline",
103
182
  },
104
183
  }
105
184
  `;
106
185
 
107
186
  exports[`byline transformation ignores extra authors in annotations array 1`] = `
108
187
  Object {
109
- "references": Array [],
188
+ "references": Array [
189
+ Object {
190
+ "contentApiData": undefined,
191
+ "reference": Object {
192
+ "children": Array [
193
+ Object {
194
+ "type": "text",
195
+ "value": "Martin Wolf",
196
+ },
197
+ ],
198
+ "data": Object {
199
+ "referenceIndex": 0,
200
+ },
201
+ "id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
202
+ "type": "author",
203
+ },
204
+ },
205
+ ],
110
206
  "tree": Object {
111
207
  "children": Array [
112
208
  Object {
@@ -116,22 +212,42 @@ Object {
116
212
  "value": "Martin Wolf",
117
213
  },
118
214
  ],
119
- "href": "https://www.ft.com/stream/uuid/7c1e1e72-57ae-4461-862a-f8d24dd42e22",
120
- "type": "author-link",
215
+ "data": Object {
216
+ "referenceIndex": 0,
217
+ },
218
+ "id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
219
+ "type": "author",
121
220
  },
122
221
  Object {
123
222
  "type": "text",
124
223
  "value": " in London",
125
224
  },
126
225
  ],
127
- "type": "root",
226
+ "type": "byline",
128
227
  },
129
228
  }
130
229
  `;
131
230
 
132
231
  exports[`byline transformation ignores unknown authors in byline text 1`] = `
133
232
  Object {
134
- "references": Array [],
233
+ "references": Array [
234
+ Object {
235
+ "contentApiData": undefined,
236
+ "reference": Object {
237
+ "children": Array [
238
+ Object {
239
+ "type": "text",
240
+ "value": "Chris Giles",
241
+ },
242
+ ],
243
+ "data": Object {
244
+ "referenceIndex": 0,
245
+ },
246
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
247
+ "type": "author",
248
+ },
249
+ },
250
+ ],
135
251
  "tree": Object {
136
252
  "children": Array [
137
253
  Object {
@@ -141,22 +257,42 @@ Object {
141
257
  "value": "Chris Giles",
142
258
  },
143
259
  ],
144
- "href": "https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28",
145
- "type": "author-link",
260
+ "data": Object {
261
+ "referenceIndex": 0,
262
+ },
263
+ "id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
264
+ "type": "author",
146
265
  },
147
266
  Object {
148
267
  "type": "text",
149
268
  "value": " and Nick Ramsbottom in London",
150
269
  },
151
270
  ],
152
- "type": "root",
271
+ "type": "byline",
153
272
  },
154
273
  }
155
274
  `;
156
275
 
157
276
  exports[`byline transformation leaves curly apostrophes in byline 1`] = `
158
277
  Object {
159
- "references": Array [],
278
+ "references": Array [
279
+ Object {
280
+ "contentApiData": undefined,
281
+ "reference": Object {
282
+ "children": Array [
283
+ Object {
284
+ "type": "text",
285
+ "value": "Sarah O’Connor",
286
+ },
287
+ ],
288
+ "data": Object {
289
+ "referenceIndex": 0,
290
+ },
291
+ "id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
292
+ "type": "author",
293
+ },
294
+ },
295
+ ],
160
296
  "tree": Object {
161
297
  "children": Array [
162
298
  Object {
@@ -166,15 +302,18 @@ Object {
166
302
  "value": "Sarah O’Connor",
167
303
  },
168
304
  ],
169
- "href": "https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
170
- "type": "author-link",
305
+ "data": Object {
306
+ "referenceIndex": 0,
307
+ },
308
+ "id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
309
+ "type": "author",
171
310
  },
172
311
  Object {
173
312
  "type": "text",
174
313
  "value": " in Bali",
175
314
  },
176
315
  ],
177
- "type": "root",
316
+ "type": "byline",
178
317
  },
179
318
  }
180
319
  `;
@@ -24,6 +24,7 @@ const articleMetadataSchema = baseMetadataSchema.pick({
24
24
  publishedDate: true,
25
25
  firstPublishedDate: true,
26
26
  publishReference: true,
27
+ lastModified: true,
27
28
  accessLevel: true,
28
29
  editorialDesk: true,
29
30
  canBeSyndicated: true,
@@ -19,6 +19,7 @@ const audioMetadataSchema = baseMetadataSchema.pick({
19
19
  publishedDate: true,
20
20
  firstPublishedDate: true,
21
21
  publishReference: true,
22
+ lastModified: true,
22
23
  accessLevel: true,
23
24
  editorialDesk: true,
24
25
  canBeSyndicated: true,
@@ -89,7 +89,7 @@ const AlternativeImage = z.object({
89
89
  promotionalImage: BaseImage,
90
90
  })
91
91
 
92
- export const LeadFlourish = z.object({
92
+ const LeadFlourish = z.object({
93
93
  id: z.string(),
94
94
  type: z.string(),
95
95
  description: z.string(),
@@ -150,7 +150,7 @@ const DataSource = z.object({
150
150
  mediaType: z.string(),
151
151
  })
152
152
 
153
- export const CapiPersonMembership = z.object({
153
+ const CapiPersonMembership = z.object({
154
154
  title: z.string(),
155
155
  types: z.string().array(),
156
156
  directType: z.string(),
@@ -165,7 +165,7 @@ export const CapiPerson = z.object({
165
165
  types: z.string().array(),
166
166
  directType: z.string(),
167
167
  labels: z.string().array(),
168
- memberships: CapiPersonMembership.array(),
168
+ memberships: CapiPersonMembership.array().optional(),
169
169
  emailAddress: z.string().optional(),
170
170
  twitterHandle: z.string().optional(),
171
171
  descriptionXML: z.string().optional(),
@@ -195,6 +195,7 @@ export const baseMetadataSchema = z.object({
195
195
  publishedDate: z.string(),
196
196
  firstPublishedDate: z.string(),
197
197
  publishReference: z.string().optional(),
198
+ lastModified: z.string(),
198
199
  realtime: z.boolean(),
199
200
  editorialDesk: z.string().optional(),
200
201
  accessLevel: z
@@ -12,6 +12,7 @@ const contentPackageContentSchema = baseContentSchema
12
12
  contains: true,
13
13
  design: true,
14
14
  bodyXML: true,
15
+ byline: true,
15
16
  })
16
17
  .merge(
17
18
  baseContentSchema
@@ -31,6 +32,7 @@ const contentPackageMetadataSchema = baseMetadataSchema.pick({
31
32
  standout: true,
32
33
  publishedDate: true,
33
34
  firstPublishedDate: true,
35
+ lastModified: true,
34
36
  publishReference: true,
35
37
  accessLevel: true,
36
38
  editorialDesk: true,
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod'
2
+ import type {
3
+ CapiPerson,
4
+ Annotation,
5
+ Image,
6
+ ImageSet,
7
+ Clip,
8
+ ClipSet,
9
+ MainImage,
10
+ LeadImage,
11
+ } from './base-schema'
12
+
13
+ import { articleSchema } from './article'
14
+ import { placeholderSchema } from './placeholder'
15
+
16
+ import { liveBlogPackageSchema } from './live-blog-package'
17
+ import { audioSchema } from './audio'
18
+ import { videoSchema } from './video'
19
+
20
+ import { contentPackageSchema } from './content-package'
21
+
22
+ export type Article = z.infer<typeof articleSchema>
23
+ type Placeholder = z.infer<typeof placeholderSchema>
24
+ type LiveBlogPackage = z.infer<typeof liveBlogPackageSchema>
25
+ type ContentPackage = z.infer<typeof contentPackageSchema>
26
+ type Audio = z.infer<typeof audioSchema>
27
+ type Video = z.infer<typeof videoSchema>
28
+
29
+ export type CapiPerson = z.infer<typeof CapiPerson>
30
+
31
+ export type Annotation = z.infer<typeof Annotation>
32
+ export type Image = z.infer<typeof Image>
33
+ export type LeadImage = z.infer<typeof LeadImage>
34
+ export type ImageSet = z.infer<typeof ImageSet>
35
+ export type MainImage = z.infer<typeof MainImage>
36
+ export type Clip = z.infer<typeof Clip>
37
+ export type ClipSet = z.infer<typeof ClipSet>
38
+
39
+ export type ContentTypeSchemas =
40
+ | Article
41
+ | Placeholder
42
+ | LiveBlogPackage
43
+ | ContentPackage
44
+ | Audio
45
+ | Video
@@ -23,6 +23,7 @@ const liveBlogPackageMetadataSchema = baseMetadataSchema.pick({
23
23
  standout: true,
24
24
  publishedDate: true,
25
25
  firstPublishedDate: true,
26
+ lastModified: true,
26
27
  publishReference: true,
27
28
  accessLevel: true,
28
29
  editorialDesk: true,
@@ -23,6 +23,7 @@ const placeholderMetadataSchema = baseMetadataSchema.pick({
23
23
  standout: true,
24
24
  publishedDate: true,
25
25
  firstPublishedDate: true,
26
+ lastModified: true,
26
27
  publishReference: true,
27
28
  accessLevel: true,
28
29
  editorialDesk: true,
@@ -8,6 +8,7 @@ const videoContentSchema = baseContentSchema.pick({
8
8
  title: true,
9
9
  transcript: true,
10
10
  alternativeTitles: true,
11
+ byline: true,
11
12
  })
12
13
 
13
14
  const videoMetadataSchema = baseMetadataSchema.pick({
@@ -18,6 +19,7 @@ const videoMetadataSchema = baseMetadataSchema.pick({
18
19
  types: true,
19
20
  publishedDate: true,
20
21
  firstPublishedDate: true,
22
+ lastModified: true,
21
23
  publishReference: true,
22
24
  accessLevel: true,
23
25
  editorialDesk: true,
@@ -1,17 +1,34 @@
1
- import { ConceptResolvers } from '../generated'
1
+ import {
2
+ ConceptInterfaceResolvers,
3
+ ConceptResolvers,
4
+ AuthorResolvers,
5
+ } from '../generated'
2
6
 
3
- const resolvers: { Concept: ConceptResolvers } = {
4
- Concept: {
5
- apiUrl: (parent) => parent.apiUrl(),
6
- directType: (parent) => parent.directType(),
7
- isPackageBrand: (parent) => parent.isPackageBrand(),
8
- id: (parent) => parent.uuid(),
9
- predicate: (parent) => parent.predicate(),
10
- prefLabel: (parent) => parent.prefLabel(),
11
- type: (parent) => parent.type(),
12
- types: (parent) => parent.types(),
13
- url: async (parent, args) => parent.url(args),
7
+ const conceptResolvers = {
8
+ __resolveType: (parent) => (parent.isAuthor() ? 'Author' : 'Concept'),
9
+ apiUrl: (parent) => parent.apiUrl(),
10
+ directType: (parent) => parent.directType(),
11
+ isPackageBrand: (parent) => parent.isPackageBrand(),
12
+ id: (parent) => parent.uuid(),
13
+ predicate: (parent) => parent.predicate(),
14
+ prefLabel: (parent) => parent.prefLabel(),
15
+ type: (parent) => parent.type(),
16
+ types: (parent) => parent.types(),
17
+ url: async (parent, args) => parent.url(args),
18
+ } satisfies ConceptInterfaceResolvers
19
+
20
+ const resolvers = {
21
+ ConceptInterface: conceptResolvers,
22
+ Concept: conceptResolvers,
23
+ Author: {
24
+ ...conceptResolvers,
25
+ person: (parent, _, context) =>
26
+ context.dataSources.capi.getPerson(parent.uuid()),
14
27
  },
28
+ } satisfies {
29
+ ConceptInterface: ConceptInterfaceResolvers
30
+ Concept: ConceptResolvers
31
+ Author: AuthorResolvers
15
32
  }
16
33
 
17
34
  export default resolvers