@financial-times/cp-content-pipeline-schema 3.4.0 → 3.5.1

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 (113) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/datasources/capi.d.ts +0 -2
  3. package/lib/datasources/capi.js +10 -23
  4. package/lib/datasources/capi.js.map +1 -1
  5. package/lib/datasources/instrumented.js +4 -1
  6. package/lib/datasources/instrumented.js.map +1 -1
  7. package/lib/datasources/origami-image.d.ts +0 -2
  8. package/lib/datasources/origami-image.js +5 -19
  9. package/lib/datasources/origami-image.js.map +1 -1
  10. package/lib/datasources/twitter.d.ts +0 -2
  11. package/lib/datasources/twitter.js +2 -16
  12. package/lib/datasources/twitter.js.map +1 -1
  13. package/lib/datasources/url-management.js +5 -3
  14. package/lib/datasources/url-management.js.map +1 -1
  15. package/lib/fixtures/dummyContext.js +1 -1
  16. package/lib/generated/index.d.ts +73 -22
  17. package/lib/index.d.ts +1 -0
  18. package/lib/index.js.map +1 -1
  19. package/lib/model/Byline.js +11 -14
  20. package/lib/model/Byline.js.map +1 -1
  21. package/lib/model/CapiList.js +2 -0
  22. package/lib/model/CapiList.js.map +1 -1
  23. package/lib/model/CapiResponse.d.ts +7 -3
  24. package/lib/model/CapiResponse.js +125 -57
  25. package/lib/model/CapiResponse.js.map +1 -1
  26. package/lib/model/CapiResponse.test.js +92 -7
  27. package/lib/model/CapiResponse.test.js.map +1 -1
  28. package/lib/model/Clip.js +2 -0
  29. package/lib/model/Clip.js.map +1 -1
  30. package/lib/model/Concept.js +4 -10
  31. package/lib/model/Concept.js.map +1 -1
  32. package/lib/model/FlourishSource.d.ts +12 -5
  33. package/lib/model/FlourishSource.js +37 -38
  34. package/lib/model/FlourishSource.js.map +1 -1
  35. package/lib/model/FlourishSource.test.js +5 -5
  36. package/lib/model/FlourishSource.test.js.map +1 -1
  37. package/lib/model/Image.js +22 -20
  38. package/lib/model/Image.js.map +1 -1
  39. package/lib/model/LeadFlourish.d.ts +1 -1
  40. package/lib/model/LeadFlourish.js +8 -11
  41. package/lib/model/LeadFlourish.js.map +1 -1
  42. package/lib/model/LeadFlourish.test.js +5 -7
  43. package/lib/model/LeadFlourish.test.js.map +1 -1
  44. package/lib/model/Person.js +5 -16
  45. package/lib/model/Person.js.map +1 -1
  46. package/lib/model/Picture.js +3 -0
  47. package/lib/model/Picture.js.map +1 -1
  48. package/lib/model/RichText.js +3 -0
  49. package/lib/model/RichText.js.map +1 -1
  50. package/lib/model/Topper.js +5 -16
  51. package/lib/model/Topper.js.map +1 -1
  52. package/lib/model/schemas/capi/article.d.ts +3 -3
  53. package/lib/model/schemas/capi/audio.d.ts +5 -5
  54. package/lib/model/schemas/capi/base-schema.d.ts +5 -5
  55. package/lib/model/schemas/capi/base-schema.js +1 -1
  56. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  57. package/lib/model/schemas/capi/content-package.d.ts +3 -3
  58. package/lib/model/schemas/capi/custom-code-component.d.ts +3 -3
  59. package/lib/model/schemas/capi/index.d.ts +20 -20
  60. package/lib/model/schemas/capi/internal-content.d.ts +1 -1
  61. package/lib/model/schemas/capi/live-blog-package.d.ts +3 -3
  62. package/lib/model/schemas/capi/placeholder.d.ts +3 -3
  63. package/lib/model/schemas/capi/video.d.ts +3 -3
  64. package/lib/resolvers/content-tree/references/Flourish.d.ts +9 -19
  65. package/lib/resolvers/content-tree/references/Flourish.js +10 -32
  66. package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
  67. package/lib/resolvers/content-tree/references/Flourish.test.js +3 -3
  68. package/lib/resolvers/content-tree/references/Flourish.test.js.map +1 -1
  69. package/lib/resolvers/content-tree/references/RawImage.js +2 -0
  70. package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
  71. package/lib/resolvers/content-tree/references/index.d.ts +2 -2
  72. package/lib/resolvers/content-tree/references/index.js +1 -1
  73. package/lib/resolvers/content-tree/references/index.js.map +1 -1
  74. package/lib/resolvers/content.d.ts +26 -1
  75. package/lib/resolvers/content.js +21 -1
  76. package/lib/resolvers/content.js.map +1 -1
  77. package/lib/resolvers/index.d.ts +27 -3
  78. package/lib/resolvers/leadFlourish.d.ts +1 -1
  79. package/lib/resolvers/literal-union.js +1 -0
  80. package/lib/resolvers/literal-union.js.map +1 -1
  81. package/lib/types/connection.d.ts +21 -0
  82. package/lib/types/connection.js +5 -0
  83. package/lib/types/connection.js.map +1 -0
  84. package/package.json +1 -1
  85. package/queries/article.graphql +8 -2
  86. package/src/datasources/capi.ts +1 -14
  87. package/src/datasources/origami-image.ts +1 -13
  88. package/src/datasources/twitter.ts +1 -14
  89. package/src/fixtures/dummyContext.ts +1 -1
  90. package/src/generated/index.ts +75 -24
  91. package/src/index.ts +1 -0
  92. package/src/model/CapiResponse.test.ts +137 -7
  93. package/src/model/CapiResponse.ts +129 -37
  94. package/src/model/FlourishSource.test.ts +5 -5
  95. package/src/model/FlourishSource.ts +57 -39
  96. package/src/model/Image.ts +16 -0
  97. package/src/model/LeadFlourish.test.ts +6 -9
  98. package/src/model/LeadFlourish.ts +5 -1
  99. package/src/model/schemas/capi/base-schema.ts +1 -1
  100. package/src/model/schemas/capi/internal-content.ts +1 -1
  101. package/src/resolvers/content-tree/references/Flourish.test.ts +4 -3
  102. package/src/resolvers/content-tree/references/Flourish.ts +16 -32
  103. package/src/resolvers/content-tree/references/index.ts +4 -4
  104. package/src/resolvers/content.ts +31 -1
  105. package/src/types/connection.ts +28 -0
  106. package/tsconfig.tsbuildinfo +1 -1
  107. package/typedefs/content.graphql +40 -2
  108. package/typedefs/leadFlourish.graphql +1 -1
  109. package/typedefs/references/flourish.graphql +1 -11
  110. package/lib/helpers/timeout-error.d.ts +0 -6
  111. package/lib/helpers/timeout-error.js +0 -15
  112. package/lib/helpers/timeout-error.js.map +0 -1
  113. package/src/helpers/timeout-error.ts +0 -13
@@ -45,7 +45,7 @@ describe('FlourishSource', () => {
45
45
 
46
46
  it('should return the correct flourishUrl', () => {
47
47
  expect(flourishSource.flourishUrl()).toBe(
48
- 'https://public.flourish.studio/test-type/1234/thumbnail?cacheBuster=5717196'
48
+ 'https://public.flourish.studio/test-type/1234/thumbnail?cacheBuster=952866'
49
49
  )
50
50
  })
51
51
 
@@ -70,9 +70,9 @@ describe('FlourishSource', () => {
70
70
  expect(decodeURIComponent(url)).toMatch(flourishImageUrlRegex)
71
71
  })
72
72
 
73
- it('should bust the cache of the fallback image every five minutes', async () => {
74
- const oldCacheBuster = '5717196'
75
- const newCacheBuster = '5717197'
73
+ it('should bust the cache of the fallback image every thirty minutes', async () => {
74
+ const oldCacheBuster = '952866'
75
+ const newCacheBuster = '952867'
76
76
 
77
77
  expect(flourishSource.flourishUrl()).toBe(
78
78
  `https://public.flourish.studio/test-type/1234/thumbnail?cacheBuster=${oldCacheBuster}`
@@ -85,7 +85,7 @@ describe('FlourishSource', () => {
85
85
  )
86
86
 
87
87
  newFlourishSource = new FlourishSource(mockFlourishData, context)
88
- jest.spyOn(Date, 'now').mockImplementation(() => 1715159100000) // 8 May 2024 09:05:00
88
+ jest.spyOn(Date, 'now').mockImplementation(() => 1715160840000) // 8 May 2024 09:34:00
89
89
  expect(newFlourishSource.flourishUrl()).toBe(
90
90
  `https://public.flourish.studio/test-type/1234/thumbnail?cacheBuster=${newCacheBuster}`
91
91
  )
@@ -8,19 +8,37 @@ type ImageMetadata = {
8
8
  height: number
9
9
  }
10
10
 
11
- type FlourishData =
12
- | {
13
- id: string
14
- type?: string
15
- description?: string
16
- }
17
- | null
18
- | undefined
11
+ type FlourishData = {
12
+ id: string
13
+ type?: string
14
+ description?: string
15
+ width?: number
16
+ height?: number
17
+ }
18
+
19
+ const DEFAULT_WIDTH = 2626
20
+ const DEFAULT_HEIGHT = 1459
19
21
 
20
22
  export class FlourishSource {
21
23
  #systemCode: string
22
24
  private imageMetadata?: ImageMetadata
23
25
 
26
+ static async createWithMetadata(data: FlourishData, context: QueryContext) {
27
+ const { width, height } = await FlourishSource.getImageMetadata(
28
+ data,
29
+ context
30
+ )
31
+
32
+ return new FlourishSource(
33
+ {
34
+ ...data,
35
+ width,
36
+ height,
37
+ },
38
+ context
39
+ )
40
+ }
41
+
24
42
  constructor(
25
43
  private flourishData: FlourishData,
26
44
  private context: QueryContext
@@ -29,11 +47,11 @@ export class FlourishSource {
29
47
  }
30
48
 
31
49
  id() {
32
- return this.flourishData?.id || ''
50
+ return this.flourishData.id
33
51
  }
34
52
 
35
53
  type() {
36
- return this.flourishData?.type || 'flourish'
54
+ return this.flourishData.type ?? 'flourish'
37
55
  }
38
56
 
39
57
  format() {
@@ -41,63 +59,63 @@ export class FlourishSource {
41
59
  }
42
60
 
43
61
  flourishUrl() {
44
- // Timestamp rounded to the nearest 5 minutes to bust the cache every five minutes
45
- const cacheBusterTimestamp = Math.floor(Date.now() / (5 * 60 * 1000))
46
-
47
- return `https://public.flourish.studio/${this.type()}/${this.id()}/thumbnail?cacheBuster=${cacheBusterTimestamp}`
62
+ return FlourishSource.fallbackUrl(this.flourishData)
48
63
  }
49
64
 
50
- private async getImageMetadata(): Promise<ImageMetadata> {
51
- const DEFAULT_WIDTH = 2626
52
- const DEFAULT_HEIGHT = 1459
65
+ private static fallbackUrl(data: FlourishData) {
66
+ // Timestamp rounded to the nearest 30 minutes to bust the cache every half an hour
67
+ const cacheBusterTimestamp = Math.floor(Date.now() / (30 * 60 * 1000))
53
68
 
54
- if (this.imageMetadata) {
55
- return this.imageMetadata
56
- }
69
+ return `https://public.flourish.studio/${data.type ?? 'flourish'}/${
70
+ data.id
71
+ }/thumbnail?cacheBuster=${cacheBusterTimestamp}`
72
+ }
73
+
74
+ static async getImageMetadata(
75
+ data: FlourishData,
76
+ context: QueryContext
77
+ ) {
78
+ const url = FlourishSource.fallbackUrl(data)
57
79
 
58
80
  try {
59
- const metadata = await this.context.dataSources.origami.getImageMetadata(
60
- this.flourishUrl()
61
- )
62
- this.imageMetadata = {
81
+ const metadata = await context.dataSources.origami.getImageMetadata(url)
82
+
83
+ return {
63
84
  width: metadata?.width || DEFAULT_WIDTH,
64
85
  height: metadata?.height || DEFAULT_HEIGHT,
65
86
  }
66
87
  } catch (error) {
67
88
  if (isError(error)) {
68
- this.context.logger.warn({
89
+ context.logger.warn({
69
90
  event: 'RECOVERABLE_ERROR',
70
91
  error: new OperationalError({
71
92
  code: 'FLOURISH_IMAGE_METADATA_ERROR',
72
- message: `Error getting image dimensions for Flourish fallback image ${this.flourishUrl()}`,
93
+ message: `Error getting image dimensions for Flourish fallback image ${url}`,
73
94
  cause: error,
74
95
  }),
75
96
  })
76
97
  }
77
- this.imageMetadata = {
98
+
99
+ return {
78
100
  width: DEFAULT_WIDTH,
79
101
  height: DEFAULT_HEIGHT,
80
102
  }
81
103
  }
82
- return this.imageMetadata
83
104
  }
84
- async width() {
85
- const imageMetadata = await this.getImageMetadata()
86
- return imageMetadata.width
105
+
106
+ width() {
107
+ return this.flourishData.width ?? DEFAULT_WIDTH
87
108
  }
88
109
 
89
- async height() {
90
- const imageMetadata = await this.getImageMetadata()
91
- return imageMetadata.height
110
+ height() {
111
+ return this.flourishData.height ?? DEFAULT_HEIGHT
92
112
  }
93
113
 
94
- async url() {
95
- const width = await this.width()
96
- const imageServiceWrappedUrl = imageServiceUrl({
114
+ url() {
115
+ return imageServiceUrl({
97
116
  url: this.flourishUrl(),
98
117
  systemCode: 'cp-content-pipeline',
99
- width,
118
+ width: this.width(),
100
119
  })
101
- return imageServiceWrappedUrl
102
120
  }
103
121
  }
@@ -157,6 +157,21 @@ export class CAPIImage implements Image {
157
157
  height: this.capiImage.pixelHeight,
158
158
  }
159
159
  }
160
+
161
+ // HACK:KB:20241029 graphics don't publish pixelWidth/pixelHeight due to a
162
+ // known issue in Spark. we want to avoid calling Image Service to improve
163
+ // performance. skip calling Image Service for graphics for the time being
164
+ // https://financialtimes.slack.com/archives/C02NXHEEN7J/p1729770129991129
165
+ if(this.type() === 'graphic') {
166
+ this.context.logger.warn({
167
+ event: 'SKIPPING_GRAPHICS_DIMENSIONS',
168
+ message: 'Not calling Image Service to get graphics dimensions',
169
+ url: this.capiImage.binaryUrl
170
+ })
171
+
172
+ return null
173
+ }
174
+
160
175
  try {
161
176
  const imageMetadata = await this.#dataSources.origami.getImageMetadata(
162
177
  this.capiImage.binaryUrl
@@ -174,6 +189,7 @@ export class CAPIImage implements Image {
174
189
  }),
175
190
  })
176
191
  }
192
+
177
193
  return null
178
194
  }
179
195
  }
@@ -7,18 +7,13 @@ import cloneDeep from 'clone-deep'
7
7
 
8
8
  const context = {} as unknown as QueryContext
9
9
 
10
- jest.mock('./FlourishSource', () => {
11
- return {
12
- FlourishSource: jest.fn().mockImplementation(() => ({})),
13
- }
14
- })
15
-
16
10
  const leadFlourishData = {
17
11
  id: 'test-id',
18
12
  description: 'test-description',
19
13
  type: 'test-type',
20
14
  }
21
15
 
16
+
22
17
  describe('LeadFlourish', () => {
23
18
  let leadFlourish: LeadFlourish
24
19
 
@@ -30,15 +25,17 @@ describe('LeadFlourish', () => {
30
25
  layoutWidth: 'full-width',
31
26
  backgroundColour: 'paper',
32
27
  }
28
+ jest.spyOn(FlourishSource, 'getImageMetadata').mockResolvedValue({
29
+ width: 800,
30
+ height: 600
31
+ })
33
32
  const capiResponse = new CapiResponse(clonedBase, context)
34
33
  leadFlourish = new LeadFlourish(capiResponse, context)
35
34
  })
36
35
 
37
36
  describe('fallbackImage', () => {
38
37
  it('should instantiate a new FlourishSource', async () => {
39
- await leadFlourish.fallbackImage()
40
-
41
- expect(FlourishSource).toHaveBeenCalledWith(leadFlourishData, context)
38
+ await expect(leadFlourish.fallbackImage()).resolves.toBeInstanceOf(FlourishSource)
42
39
  })
43
40
  })
44
41
 
@@ -13,7 +13,11 @@ export class LeadFlourish {
13
13
 
14
14
  async fallbackImage() {
15
15
  const flourishData = this.capiResponse.leadFlourish()
16
- return new FlourishSource(flourishData, this.context)
16
+ if (!flourishData) {
17
+ return null
18
+ }
19
+
20
+ return FlourishSource.createWithMetadata(flourishData, this.context)
17
21
  }
18
22
 
19
23
  id() {
@@ -147,7 +147,7 @@ export const ClipSet = z.object({
147
147
  const DataSource = z.object({
148
148
  binaryUrl: z.string(),
149
149
  duration: z.number(),
150
- filesize: z.number(),
150
+ filesize: z.number().optional(),
151
151
  mediaType: z.string(),
152
152
  })
153
153
 
@@ -23,7 +23,7 @@ import { contentPackageSchema } from './content-package'
23
23
 
24
24
  export type Article = z.infer<typeof articleSchema>
25
25
  type Placeholder = z.infer<typeof placeholderSchema>
26
- type LiveBlogPackage = z.infer<typeof liveBlogPackageSchema>
26
+ export type LiveBlogPackage = z.infer<typeof liveBlogPackageSchema>
27
27
  type ContentPackage = z.infer<typeof contentPackageSchema>
28
28
  type Audio = z.infer<typeof audioSchema>
29
29
  type Video = z.infer<typeof videoSchema>
@@ -30,6 +30,7 @@ describe('Flourish Model', () => {
30
30
  timestamp: '2022-01-06T14:41:01.102Z',
31
31
  description: 'Example description',
32
32
  }
33
+
33
34
  const fallbackResponse = await Flourish.fallbackImage(
34
35
  { reference },
35
36
  {},
@@ -42,7 +43,7 @@ describe('Flourish Model', () => {
42
43
  /https:\/\/public.flourish.studio\/.*\/.*\/thumbnail/
43
44
  )
44
45
 
45
- expect(decodeURIComponent(fallbackResponse.url as string)).toMatch(
46
+ expect(decodeURIComponent(fallbackResponse?.url() as string)).toMatch(
46
47
  flourishImageUrlRegex
47
48
  )
48
49
  })
@@ -66,7 +67,7 @@ describe('Flourish Model', () => {
66
67
  /https:\/\/public.flourish.studio\/.*\/example-id/
67
68
  )
68
69
 
69
- expect(decodeURIComponent(fallbackResponse.url as string)).toMatch(
70
+ expect(decodeURIComponent(fallbackResponse?.url() as string)).toMatch(
70
71
  flourishImageUrlRegex
71
72
  )
72
73
  })
@@ -88,7 +89,7 @@ describe('Flourish Model', () => {
88
89
 
89
90
  const flourishImageUrl = 'https://public.flourish.studio/example-type'
90
91
 
91
- expect(fallbackResponse.url).toContain(
92
+ expect(fallbackResponse?.url() ?? '').toContain(
92
93
  encodeURIComponent(flourishImageUrl)
93
94
  )
94
95
  })
@@ -1,38 +1,25 @@
1
- import imageServiceUrl from '../../../helpers/imageService'
2
1
  import {
3
- FlourishFallbackResolvers,
4
2
  FlourishResolvers,
3
+ FlourishSourceResolvers,
5
4
  } from '../../../generated'
6
- import { FlourishSource } from '../../../model/FlourishSource'
5
+ import { FlourishSource as FlourishSourceModel } from '../../../model/FlourishSource'
7
6
 
8
7
  export const Flourish = {
9
8
  async fallbackImage(parent, _args, context) {
10
9
  if (!parent.reference) {
11
- return {}
10
+ return null
12
11
  }
12
+
13
13
  const flourishData = {
14
14
  id: parent.reference.id,
15
15
  type: parent.reference.flourishType,
16
16
  description: parent.reference.description,
17
17
  }
18
- const flourishSource = new FlourishSource(flourishData, context)
19
- const width = await flourishSource.width()
20
- const height = await flourishSource.height()
21
-
22
- const imageServiceWrappedUrl = imageServiceUrl({
23
- url: flourishSource.flourishUrl(),
24
- systemCode: context.systemCode ?? 'cp-content-pipeline',
25
- width,
26
- })
27
18
 
28
- return {
29
- url: imageServiceWrappedUrl,
30
- type: flourishSource.type(),
31
- format: flourishSource.format(),
32
- sourceSet: [],
33
- width,
34
- height,
35
- }
19
+ return FlourishSourceModel.createWithMetadata(
20
+ flourishData,
21
+ context
22
+ )
36
23
  },
37
24
 
38
25
  type(parent) {
@@ -40,14 +27,11 @@ export const Flourish = {
40
27
  },
41
28
  } satisfies FlourishResolvers
42
29
 
43
- export const FlourishFallback = {
44
- async height(parent) {
45
- return parent.height ?? null
46
- },
47
- async width(parent) {
48
- return parent.width ?? null
49
- },
50
- async url(parent) {
51
- return parent.url ?? null
52
- },
53
- } satisfies FlourishFallbackResolvers
30
+ export const FlourishSource = {
31
+ id: (parent) => parent.id(),
32
+ url: (parent) => parent.flourishUrl(),
33
+ type: (parent) => parent.type(),
34
+ width: (parent) => parent.width(),
35
+ height: (parent) => parent.height(),
36
+ format: (parent) => parent.format(),
37
+ } satisfies FlourishSourceResolvers
@@ -8,7 +8,7 @@ import { ImageSet } from './ImageSet'
8
8
  import { ClipSet, Accessibility, Caption } from './ClipSet'
9
9
  import { CustomCodeComponent } from './CustomCodeComponent'
10
10
  import { Video } from './Video'
11
- import { Flourish, FlourishFallback } from './Flourish'
11
+ import { Flourish, FlourishSource } from './Flourish'
12
12
  import { Recommended } from './Recommended'
13
13
  import { LayoutImage } from './LayoutImage'
14
14
  import { RawImage } from './RawImage'
@@ -27,8 +27,8 @@ import {
27
27
  VideoReferenceResolvers,
28
28
  CaptionResolvers,
29
29
  AccessibilityResolvers,
30
- FlourishFallbackResolvers,
31
30
  AuthorReferenceResolvers,
31
+ FlourishSourceResolvers,
32
32
  } from '../../../generated'
33
33
  import { AnyNode } from '../Workarounds'
34
34
  import { Author } from './Author'
@@ -46,6 +46,7 @@ export const resolvers: {
46
46
  CustomCodeComponent: CustomCodeComponentResolvers
47
47
  VideoReference: VideoReferenceResolvers
48
48
  Flourish: FlourishResolvers
49
+ FlourishSource: FlourishSourceResolvers
49
50
  Recommended: RecommendedResolvers
50
51
  LayoutImage: LayoutImageResolvers
51
52
  RawImage: RawImageResolvers
@@ -53,7 +54,6 @@ export const resolvers: {
53
54
  MainImage: ImageSetResolvers
54
55
  Caption: CaptionResolvers
55
56
  Accessibility: AccessibilityResolvers
56
- FlourishFallback: FlourishFallbackResolvers
57
57
  AuthorReference: AuthorReferenceResolvers
58
58
  } = {
59
59
  Reference,
@@ -63,6 +63,7 @@ export const resolvers: {
63
63
  CustomCodeComponent,
64
64
  VideoReference: Video,
65
65
  Flourish,
66
+ FlourishSource,
66
67
  Recommended,
67
68
  LayoutImage,
68
69
  RawImage,
@@ -70,7 +71,6 @@ export const resolvers: {
70
71
  MainImage: ImageSet,
71
72
  Caption,
72
73
  Accessibility,
73
- FlourishFallback,
74
74
  AuthorReference: Author,
75
75
  }
76
76
 
@@ -5,6 +5,9 @@ import {
5
5
  AudioResolvers,
6
6
  ContentPackageResolvers,
7
7
  ContentResolvers,
8
+ ContentConnectionResolvers,
9
+ ContentEdgeResolvers,
10
+ PageInfoResolvers,
8
11
  LiveBlogPackageResolvers,
9
12
  LiveBlogPostResolvers,
10
13
  VideoResolvers,
@@ -70,6 +73,23 @@ const resolvers = {
70
73
  ...contentResolvers,
71
74
  },
72
75
 
76
+ ContentConnection: {
77
+ edges: (parent) => parent.edges,
78
+ pageInfo: (parent) => parent.pageInfo,
79
+ },
80
+
81
+ ContentEdge: {
82
+ node: (parent) => parent.node,
83
+ cursor: (parent) => parent.cursor,
84
+ },
85
+
86
+ PageInfo: {
87
+ hasPreviousPage: (parent) => parent.hasPreviousPage,
88
+ hasNextPage: (parent) => parent.hasNextPage,
89
+ startCursor: (parent) => parent.startCursor ?? null,
90
+ endCursor: (parent) => parent.endCursor ?? null,
91
+ },
92
+
73
93
  Article: {
74
94
  ...contentResolvers,
75
95
  containedIn: (parent) => parent.containedIn(),
@@ -101,7 +121,14 @@ const resolvers = {
101
121
 
102
122
  LiveBlogPackage: {
103
123
  ...contentResolvers,
104
- liveBlogPosts: (parent) => parent.liveBlogPosts(),
124
+ liveBlogPosts: (parent, args) => parent.liveBlogPosts(args),
125
+ liveBlogPostsConnection: (parent, args) =>
126
+ parent.liveBlogPostsConnection({
127
+ first: args.first ?? undefined,
128
+ after: args.after ?? undefined,
129
+ last: args.last ?? undefined,
130
+ before: args.before ?? undefined,
131
+ }),
105
132
  pinnedPost: (parent) => parent.pinnedPost(),
106
133
  realtime: (parent) => parent.realtime(),
107
134
  },
@@ -161,6 +188,9 @@ const resolvers = {
161
188
  Placeholder: PlaceholderResolvers
162
189
  LiveBlogPost: LiveBlogPostResolvers
163
190
  Content: ContentResolvers
191
+ ContentConnection: ContentConnectionResolvers
192
+ ContentEdge: ContentEdgeResolvers
193
+ PageInfo: PageInfoResolvers
164
194
  LiveBlogPackage: LiveBlogPackageResolvers
165
195
  ContentPackage: ContentPackageResolvers
166
196
  Audio: AudioResolvers
@@ -0,0 +1,28 @@
1
+ // Types to aid the implementation of a Connection type, as specified in
2
+ // https://relay.dev/graphql/connections.htm
3
+
4
+ export type ConnectionCursor = string
5
+
6
+ export interface ConnectionPageInfo {
7
+ hasPreviousPage: boolean
8
+ hasNextPage: boolean
9
+ startCursor?: ConnectionCursor
10
+ endCursor?: ConnectionCursor
11
+ }
12
+
13
+ export interface ConnectionEdge<T> {
14
+ node: T
15
+ cursor: ConnectionCursor
16
+ }
17
+
18
+ export interface Connection<T> {
19
+ edges: ConnectionEdge<T>[]
20
+ pageInfo: ConnectionPageInfo
21
+ }
22
+
23
+ export interface ConnectionArguments {
24
+ first?: number
25
+ after?: ConnectionCursor
26
+ last?: number
27
+ before?: ConnectionCursor
28
+ }