@financial-times/cp-content-pipeline-schema 3.3.2 → 3.5.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.
- package/CHANGELOG.md +30 -0
- package/lib/datasources/capi.d.ts +0 -2
- package/lib/datasources/capi.js +10 -23
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/instrumented.js +4 -1
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/datasources/origami-image.d.ts +0 -2
- package/lib/datasources/origami-image.js +5 -19
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/twitter.d.ts +0 -2
- package/lib/datasources/twitter.js +2 -16
- package/lib/datasources/twitter.js.map +1 -1
- package/lib/datasources/url-management.js +5 -3
- package/lib/datasources/url-management.js.map +1 -1
- package/lib/fixtures/dummyContext.js +1 -1
- package/lib/generated/index.d.ts +72 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js.map +1 -1
- package/lib/model/Byline.js +11 -14
- package/lib/model/Byline.js.map +1 -1
- package/lib/model/CapiList.js +2 -0
- package/lib/model/CapiList.js.map +1 -1
- package/lib/model/CapiResponse.d.ts +7 -3
- package/lib/model/CapiResponse.js +125 -57
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/CapiResponse.test.js +92 -7
- package/lib/model/CapiResponse.test.js.map +1 -1
- package/lib/model/Clip.js +2 -0
- package/lib/model/Clip.js.map +1 -1
- package/lib/model/Concept.js +4 -10
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/FlourishSource.js +5 -10
- package/lib/model/FlourishSource.js.map +1 -1
- package/lib/model/Image.js +10 -20
- package/lib/model/Image.js.map +1 -1
- package/lib/model/LeadFlourish.js +4 -10
- package/lib/model/LeadFlourish.js.map +1 -1
- package/lib/model/Person.js +5 -16
- package/lib/model/Person.js.map +1 -1
- package/lib/model/Picture.js +3 -0
- package/lib/model/Picture.js.map +1 -1
- package/lib/model/RichText.js +3 -0
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/Topper.js +5 -16
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/schemas/capi/article.d.ts +5 -0
- package/lib/model/schemas/capi/audio.d.ts +3 -0
- package/lib/model/schemas/capi/base-schema.d.ts +8 -0
- package/lib/model/schemas/capi/base-schema.js +1 -0
- package/lib/model/schemas/capi/base-schema.js.map +1 -1
- package/lib/model/schemas/capi/content-package.d.ts +3 -0
- package/lib/model/schemas/capi/custom-code-component.d.ts +5 -0
- package/lib/model/schemas/capi/index.d.ts +24 -0
- package/lib/model/schemas/capi/internal-content.d.ts +1 -1
- package/lib/model/schemas/capi/live-blog-package.d.ts +5 -0
- package/lib/model/schemas/capi/placeholder.d.ts +5 -0
- package/lib/model/schemas/capi/video.d.ts +3 -0
- package/lib/resolvers/content-tree/references/ClipSet.d.ts +1 -0
- package/lib/resolvers/content-tree/references/ClipSet.js +1 -0
- package/lib/resolvers/content-tree/references/ClipSet.js.map +1 -1
- package/lib/resolvers/content-tree/references/RawImage.js +2 -0
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content.d.ts +26 -1
- package/lib/resolvers/content.js +21 -1
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/index.d.ts +26 -1
- package/lib/resolvers/literal-union.js +1 -0
- package/lib/resolvers/literal-union.js.map +1 -1
- package/lib/types/connection.d.ts +21 -0
- package/lib/types/connection.js +5 -0
- package/lib/types/connection.js.map +1 -0
- package/package.json +1 -1
- package/queries/article.graphql +9 -2
- package/src/datasources/capi.ts +1 -14
- package/src/datasources/origami-image.ts +1 -13
- package/src/datasources/twitter.ts +1 -14
- package/src/fixtures/dummyContext.ts +1 -1
- package/src/generated/index.ts +74 -1
- package/src/index.ts +1 -0
- package/src/model/CapiResponse.test.ts +137 -7
- package/src/model/CapiResponse.ts +129 -37
- package/src/model/schemas/capi/base-schema.ts +1 -0
- package/src/model/schemas/capi/internal-content.ts +1 -1
- package/src/resolvers/content-tree/references/ClipSet.ts +1 -0
- package/src/resolvers/content.ts +31 -1
- package/src/types/connection.ts +28 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +40 -2
- package/typedefs/references/clipSet.graphql +3 -0
- package/lib/helpers/timeout-error.d.ts +0 -6
- package/lib/helpers/timeout-error.js +0 -15
- package/lib/helpers/timeout-error.js.map +0 -1
- package/src/helpers/timeout-error.ts +0 -13
package/queries/article.graphql
CHANGED
|
@@ -395,6 +395,7 @@ fragment ClipSet on ClipSet {
|
|
|
395
395
|
credits
|
|
396
396
|
description
|
|
397
397
|
displayTitle
|
|
398
|
+
systemTitle
|
|
398
399
|
contentWarning
|
|
399
400
|
source
|
|
400
401
|
subtitle
|
|
@@ -664,7 +665,7 @@ fragment ArticleFields on Content {
|
|
|
664
665
|
}
|
|
665
666
|
}
|
|
666
667
|
... on LiveBlogPackage {
|
|
667
|
-
liveBlogPosts {
|
|
668
|
+
liveBlogPosts(count: $liveBlogPostCount) {
|
|
668
669
|
...Content
|
|
669
670
|
url
|
|
670
671
|
... on LiveBlogPost {
|
|
@@ -677,6 +678,11 @@ fragment ArticleFields on Content {
|
|
|
677
678
|
}
|
|
678
679
|
}
|
|
679
680
|
}
|
|
681
|
+
liveBlogPostsConnection(first: $liveBlogPostCount) {
|
|
682
|
+
pageInfo {
|
|
683
|
+
hasNextPage
|
|
684
|
+
}
|
|
685
|
+
}
|
|
680
686
|
pinnedPost {
|
|
681
687
|
...PinnedPost
|
|
682
688
|
}
|
|
@@ -695,7 +701,7 @@ fragment ArticleFields on Content {
|
|
|
695
701
|
}
|
|
696
702
|
}
|
|
697
703
|
|
|
698
|
-
query Article($uuid: String!, $useVanities: Boolean
|
|
704
|
+
query Article($uuid: String!, $useVanities: Boolean!, $liveBlogPostCount: Int) {
|
|
699
705
|
content(uuid: $uuid) {
|
|
700
706
|
...ArticleFields
|
|
701
707
|
}
|
|
@@ -704,6 +710,7 @@ query Article($uuid: String!, $useVanities: Boolean!) {
|
|
|
704
710
|
query ArticleFromJSON(
|
|
705
711
|
$content: JSON!
|
|
706
712
|
$useVanities: Boolean!
|
|
713
|
+
$liveBlogPostCount: Int
|
|
707
714
|
$uuid: String = null
|
|
708
715
|
) {
|
|
709
716
|
contentFromJSON(content: $content) {
|
package/src/datasources/capi.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
PrefixingKeyValueCache,
|
|
7
7
|
} from '@apollo/utils.keyvaluecache'
|
|
8
8
|
import { CapiList } from '../model/CapiList'
|
|
9
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
10
9
|
import { Person } from '../model/Person'
|
|
11
10
|
|
|
12
11
|
const REQUEST_TIMEOUT = process.env.CAPI_DATASOURCE_REQUEST_TIMEOUT
|
|
@@ -38,20 +37,13 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
38
37
|
return 'up-ica'
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
abortController = new AbortController()
|
|
42
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
43
|
-
|
|
44
40
|
nextNotificationLink?: string
|
|
45
41
|
|
|
46
42
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
47
43
|
super.willSendRequest(path, request)
|
|
48
44
|
|
|
45
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
49
46
|
request.headers['x-api-key'] = this.capiKey
|
|
50
|
-
request.signal = this.abortController.signal
|
|
51
|
-
this.timeout = setTimeout(
|
|
52
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
53
|
-
REQUEST_TIMEOUT
|
|
54
|
-
)
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
async getContent(
|
|
@@ -71,11 +63,6 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
71
63
|
this.context.contentRequestedOnce = true
|
|
72
64
|
this.calls.push(uuid)
|
|
73
65
|
|
|
74
|
-
if (this.timeout) {
|
|
75
|
-
clearTimeout(this.timeout)
|
|
76
|
-
this.timeout = undefined
|
|
77
|
-
}
|
|
78
|
-
|
|
79
66
|
return CapiResponse.fromJSON(content, this.context, packageContainer)
|
|
80
67
|
}
|
|
81
68
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
2
1
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
3
2
|
import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
4
3
|
|
|
@@ -12,9 +11,6 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
12
11
|
return 'origami-image-service-v2'
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
abortController = new AbortController()
|
|
16
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
17
|
-
|
|
18
14
|
imageMetadataCacheTTL = process.env.IMAGE_METADATA_CACHE_TTL
|
|
19
15
|
? parseInt(process.env.IMAGE_METADATA_CACHE_TTL)
|
|
20
16
|
: 60 * 60 * 24 * 7 // 1 week
|
|
@@ -22,11 +18,7 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
22
18
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
23
19
|
super.willSendRequest(path, request)
|
|
24
20
|
|
|
25
|
-
request.signal =
|
|
26
|
-
this.timeout = setTimeout(
|
|
27
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
28
|
-
REQUEST_TIMEOUT
|
|
29
|
-
)
|
|
21
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
protected cacheOptionsFor(): CacheOptions {
|
|
@@ -42,10 +34,6 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
42
34
|
|
|
43
35
|
this.calls.push(url)
|
|
44
36
|
|
|
45
|
-
if (this.timeout) {
|
|
46
|
-
clearTimeout(this.timeout)
|
|
47
|
-
this.timeout = undefined
|
|
48
|
-
}
|
|
49
37
|
return imageMetadata
|
|
50
38
|
}
|
|
51
39
|
}
|
|
@@ -2,7 +2,6 @@ import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
|
2
2
|
import * as z from 'zod'
|
|
3
3
|
|
|
4
4
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
5
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
6
5
|
|
|
7
6
|
const REQUEST_TIMEOUT = process.env.TWITTER_DATASOURCE_REQUEST_TIMEOUT
|
|
8
7
|
? parseInt(process.env.TWITTER_DATASOURCE_REQUEST_TIMEOUT)
|
|
@@ -16,17 +15,10 @@ export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
|
16
15
|
return 'twitter-oembed-api'
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
abortController = new AbortController()
|
|
20
|
-
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
21
|
-
|
|
22
18
|
override willSendRequest(path: string, request: AugmentedRequest) {
|
|
23
19
|
super.willSendRequest(path, request)
|
|
24
20
|
|
|
25
|
-
request.signal =
|
|
26
|
-
this.timeout = setTimeout(
|
|
27
|
-
() => this.abortController.abort(new TimeoutError(REQUEST_TIMEOUT)),
|
|
28
|
-
REQUEST_TIMEOUT
|
|
29
|
-
)
|
|
21
|
+
request.signal = AbortSignal.timeout(REQUEST_TIMEOUT)
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
async getTweet(tweetUrl: string) {
|
|
@@ -34,11 +26,6 @@ export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
|
34
26
|
|
|
35
27
|
this.calls.push(tweetUrl)
|
|
36
28
|
|
|
37
|
-
if (this.timeout) {
|
|
38
|
-
clearTimeout(this.timeout)
|
|
39
|
-
this.timeout = undefined
|
|
40
|
-
}
|
|
41
|
-
|
|
42
29
|
return Tweet.parse(tweet)
|
|
43
30
|
}
|
|
44
31
|
|
package/src/generated/index.ts
CHANGED
|
@@ -361,6 +361,8 @@ export type ClipSet = Reference & {
|
|
|
361
361
|
readonly source?: Maybe<Scalars['String']['output']>;
|
|
362
362
|
/** The subtitle of the clip set. */
|
|
363
363
|
readonly subtitle?: Maybe<Scalars['String']['output']>;
|
|
364
|
+
/** The title to be used internally for the clip set. */
|
|
365
|
+
readonly systemTitle?: Maybe<Scalars['String']['output']>;
|
|
364
366
|
/** The type of the clip set, eg. 'video'. */
|
|
365
367
|
readonly type: Scalars['String']['output'];
|
|
366
368
|
};
|
|
@@ -511,6 +513,20 @@ export type ContentUrlArgs = {
|
|
|
511
513
|
vanity?: InputMaybe<Scalars['Boolean']['input']>;
|
|
512
514
|
};
|
|
513
515
|
|
|
516
|
+
export type ContentConnection = {
|
|
517
|
+
/** A list of edges for the connection. */
|
|
518
|
+
readonly edges: ReadonlyArray<Maybe<ContentEdge>>;
|
|
519
|
+
/** Pagination data for the connection. */
|
|
520
|
+
readonly pageInfo: PageInfo;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export type ContentEdge = {
|
|
524
|
+
/** The opaque cursor for this edge that can be used for future requests. */
|
|
525
|
+
readonly cursor: Scalars['String']['output'];
|
|
526
|
+
/** The child article of this edge. */
|
|
527
|
+
readonly node?: Maybe<Content>;
|
|
528
|
+
};
|
|
529
|
+
|
|
514
530
|
export type ContentPackage = Content & {
|
|
515
531
|
/** A scalar representing the access level of the article, eg. 'free'. */
|
|
516
532
|
readonly accessLevel?: Maybe<Scalars['AccessLevel']['output']>;
|
|
@@ -1118,6 +1134,8 @@ export type LiveBlogPackage = Content & {
|
|
|
1118
1134
|
readonly instantAlertConcept?: Maybe<Concept>;
|
|
1119
1135
|
/** The child articles of this live blog package. */
|
|
1120
1136
|
readonly liveBlogPosts?: Maybe<ReadonlyArray<Maybe<Content>>>;
|
|
1137
|
+
/** The child articles of this live blog package following the [Connections](https://relay.dev/graphql/connections.htm) spec. */
|
|
1138
|
+
readonly liveBlogPostsConnection?: Maybe<ContentConnection>;
|
|
1121
1139
|
/** An image object containing the url and the caption, to be displayed usually before the article content. */
|
|
1122
1140
|
readonly mainImage?: Maybe<Image>;
|
|
1123
1141
|
/** The number of milliseconds since the unix epoch the article was last changed, eg '1712140552443'. */
|
|
@@ -1159,6 +1177,19 @@ export type LiveBlogPackageBylineArgs = {
|
|
|
1159
1177
|
};
|
|
1160
1178
|
|
|
1161
1179
|
|
|
1180
|
+
export type LiveBlogPackageLiveBlogPostsArgs = {
|
|
1181
|
+
count?: InputMaybe<Scalars['Int']['input']>;
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
export type LiveBlogPackageLiveBlogPostsConnectionArgs = {
|
|
1186
|
+
after?: InputMaybe<Scalars['String']['input']>;
|
|
1187
|
+
before?: InputMaybe<Scalars['String']['input']>;
|
|
1188
|
+
first?: InputMaybe<Scalars['Int']['input']>;
|
|
1189
|
+
last?: InputMaybe<Scalars['Int']['input']>;
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
|
|
1162
1193
|
export type LiveBlogPackageUrlArgs = {
|
|
1163
1194
|
relative?: InputMaybe<Scalars['Boolean']['input']>;
|
|
1164
1195
|
vanity?: InputMaybe<Scalars['Boolean']['input']>;
|
|
@@ -1310,6 +1341,17 @@ export type OpinionTopperHeadshotArgs = {
|
|
|
1310
1341
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1311
1342
|
};
|
|
1312
1343
|
|
|
1344
|
+
export type PageInfo = {
|
|
1345
|
+
/** The cursor for the last edge. */
|
|
1346
|
+
readonly endCursor?: Maybe<Scalars['String']['output']>;
|
|
1347
|
+
/** Whether a subsequent page of edges is available on request. */
|
|
1348
|
+
readonly hasNextPage: Scalars['Boolean']['output'];
|
|
1349
|
+
/** Whether a previous page of edges is available on request. */
|
|
1350
|
+
readonly hasPreviousPage: Scalars['Boolean']['output'];
|
|
1351
|
+
/** The cursor for the first edge. */
|
|
1352
|
+
readonly startCursor?: Maybe<Scalars['String']['output']>;
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1313
1355
|
export type PartnerContentTopper = Topper & TopperWithImages & TopperWithTheme & {
|
|
1314
1356
|
/** Whether the topper should have a background box. */
|
|
1315
1357
|
readonly backgroundBox?: Maybe<Scalars['Boolean']['output']>;
|
|
@@ -1974,6 +2016,8 @@ export type ResolversTypes = ResolversObject<{
|
|
|
1974
2016
|
Concept: ResolverTypeWrapper<ConceptModel>;
|
|
1975
2017
|
ConceptInterface: ResolverTypeWrapper<ConceptModel>;
|
|
1976
2018
|
Content: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Content']>;
|
|
2019
|
+
ContentConnection: ResolverTypeWrapper<Omit<ContentConnection, 'edges'> & { edges: ReadonlyArray<Maybe<ResolversTypes['ContentEdge']>> }>;
|
|
2020
|
+
ContentEdge: ResolverTypeWrapper<Omit<ContentEdge, 'node'> & { node: Maybe<ResolversTypes['Content']> }>;
|
|
1977
2021
|
ContentPackage: ResolverTypeWrapper<CapiResponse>;
|
|
1978
2022
|
ContentType: ResolverTypeWrapper<Scalars['ContentType']['output']>;
|
|
1979
2023
|
CustomCodeComponent: ResolverTypeWrapper<ReferenceWithCAPIData<ContentTree.CustomCodeComponent>>;
|
|
@@ -2017,6 +2061,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
2017
2061
|
Mutation: ResolverTypeWrapper<{}>;
|
|
2018
2062
|
OpinionTopper: ResolverTypeWrapper<TopperModel>;
|
|
2019
2063
|
PackageDesign: ResolverTypeWrapper<Scalars['PackageDesign']['output']>;
|
|
2064
|
+
PageInfo: ResolverTypeWrapper<PageInfo>;
|
|
2020
2065
|
PartnerContentTopper: ResolverTypeWrapper<TopperModel>;
|
|
2021
2066
|
Person: ResolverTypeWrapper<PersonModel>;
|
|
2022
2067
|
Picture: ResolverTypeWrapper<PictureModel>;
|
|
@@ -2073,6 +2118,8 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
2073
2118
|
Concept: ConceptModel;
|
|
2074
2119
|
ConceptInterface: ConceptModel;
|
|
2075
2120
|
Content: ResolversInterfaceTypes<ResolversParentTypes>['Content'];
|
|
2121
|
+
ContentConnection: Omit<ContentConnection, 'edges'> & { edges: ReadonlyArray<Maybe<ResolversParentTypes['ContentEdge']>> };
|
|
2122
|
+
ContentEdge: Omit<ContentEdge, 'node'> & { node: Maybe<ResolversParentTypes['Content']> };
|
|
2076
2123
|
ContentPackage: CapiResponse;
|
|
2077
2124
|
ContentType: Scalars['ContentType']['output'];
|
|
2078
2125
|
CustomCodeComponent: ReferenceWithCAPIData<ContentTree.CustomCodeComponent>;
|
|
@@ -2116,6 +2163,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
2116
2163
|
Mutation: {};
|
|
2117
2164
|
OpinionTopper: TopperModel;
|
|
2118
2165
|
PackageDesign: Scalars['PackageDesign']['output'];
|
|
2166
|
+
PageInfo: PageInfo;
|
|
2119
2167
|
PartnerContentTopper: TopperModel;
|
|
2120
2168
|
Person: PersonModel;
|
|
2121
2169
|
Picture: PictureModel;
|
|
@@ -2322,6 +2370,7 @@ export type ClipSetResolvers<ContextType = QueryContext, ParentType extends Reso
|
|
|
2322
2370
|
publishedDate: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2323
2371
|
source: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2324
2372
|
subtitle: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2373
|
+
systemTitle: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2325
2374
|
type: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2326
2375
|
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2327
2376
|
}>;
|
|
@@ -2393,6 +2442,18 @@ export type ContentResolvers<ContextType = QueryContext, ParentType extends Reso
|
|
|
2393
2442
|
url: Resolver<ResolversTypes['String'], ParentType, ContextType, Partial<ContentUrlArgs>>;
|
|
2394
2443
|
}>;
|
|
2395
2444
|
|
|
2445
|
+
export type ContentConnectionResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentConnection'] = ResolversParentTypes['ContentConnection']> = ResolversObject<{
|
|
2446
|
+
edges: Resolver<ReadonlyArray<Maybe<ResolversTypes['ContentEdge']>>, ParentType, ContextType>;
|
|
2447
|
+
pageInfo: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
|
|
2448
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2449
|
+
}>;
|
|
2450
|
+
|
|
2451
|
+
export type ContentEdgeResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentEdge'] = ResolversParentTypes['ContentEdge']> = ResolversObject<{
|
|
2452
|
+
cursor: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2453
|
+
node: Resolver<Maybe<ResolversTypes['Content']>, ParentType, ContextType>;
|
|
2454
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2455
|
+
}>;
|
|
2456
|
+
|
|
2396
2457
|
export type ContentPackageResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ContentPackage'] = ResolversParentTypes['ContentPackage']> = ResolversObject<{
|
|
2397
2458
|
accessLevel: Resolver<Maybe<ResolversTypes['AccessLevel']>, ParentType, ContextType>;
|
|
2398
2459
|
altStandfirst: Resolver<Maybe<ResolversTypes['AltStandfirst']>, ParentType, ContextType>;
|
|
@@ -2746,7 +2807,8 @@ export type LiveBlogPackageResolvers<ContextType = QueryContext, ParentType exte
|
|
|
2746
2807
|
firstPublishedDate: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2747
2808
|
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
|
2748
2809
|
instantAlertConcept: Resolver<Maybe<ResolversTypes['Concept']>, ParentType, ContextType>;
|
|
2749
|
-
liveBlogPosts: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Content']>>>, ParentType, ContextType
|
|
2810
|
+
liveBlogPosts: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Content']>>>, ParentType, ContextType, Partial<LiveBlogPackageLiveBlogPostsArgs>>;
|
|
2811
|
+
liveBlogPostsConnection: Resolver<Maybe<ResolversTypes['ContentConnection']>, ParentType, ContextType, Partial<LiveBlogPackageLiveBlogPostsConnectionArgs>>;
|
|
2750
2812
|
mainImage: Resolver<Maybe<ResolversTypes['Image']>, ParentType, ContextType>;
|
|
2751
2813
|
modifiedTimestamp: Resolver<Maybe<ResolversTypes['Float']>, ParentType, ContextType>;
|
|
2752
2814
|
originatingParty: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
@@ -2840,6 +2902,14 @@ export interface PackageDesignScalarConfig extends GraphQLScalarTypeConfig<Resol
|
|
|
2840
2902
|
name: 'PackageDesign';
|
|
2841
2903
|
}
|
|
2842
2904
|
|
|
2905
|
+
export type PageInfoResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['PageInfo'] = ResolversParentTypes['PageInfo']> = ResolversObject<{
|
|
2906
|
+
endCursor: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2907
|
+
hasNextPage: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2908
|
+
hasPreviousPage: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2909
|
+
startCursor: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2910
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2911
|
+
}>;
|
|
2912
|
+
|
|
2843
2913
|
export type PartnerContentTopperResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['PartnerContentTopper'] = ResolversParentTypes['PartnerContentTopper']> = ResolversObject<{
|
|
2844
2914
|
backgroundBox: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
|
2845
2915
|
backgroundColour: Resolver<Maybe<ResolversTypes['TopperBackgroundColour']>, ParentType, ContextType>;
|
|
@@ -3180,6 +3250,8 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
3180
3250
|
Concept: ConceptResolvers<ContextType>;
|
|
3181
3251
|
ConceptInterface: ConceptInterfaceResolvers<ContextType>;
|
|
3182
3252
|
Content: ContentResolvers<ContextType>;
|
|
3253
|
+
ContentConnection: ContentConnectionResolvers<ContextType>;
|
|
3254
|
+
ContentEdge: ContentEdgeResolvers<ContextType>;
|
|
3183
3255
|
ContentPackage: ContentPackageResolvers<ContextType>;
|
|
3184
3256
|
ContentType: GraphQLScalarType;
|
|
3185
3257
|
CustomCodeComponent: CustomCodeComponentResolvers<ContextType>;
|
|
@@ -3220,6 +3292,7 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
3220
3292
|
Mutation: MutationResolvers<ContextType>;
|
|
3221
3293
|
OpinionTopper: OpinionTopperResolvers<ContextType>;
|
|
3222
3294
|
PackageDesign: GraphQLScalarType;
|
|
3295
|
+
PageInfo: PageInfoResolvers<ContextType>;
|
|
3223
3296
|
PartnerContentTopper: PartnerContentTopperResolvers<ContextType>;
|
|
3224
3297
|
Person: PersonResolvers<ContextType>;
|
|
3225
3298
|
Picture: PictureResolvers<ContextType>;
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { CapiResponse } from './CapiResponse'
|
|
|
2
2
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
3
3
|
import cloneDeep from 'clone-deep'
|
|
4
4
|
import context from '../fixtures/dummyContext'
|
|
5
|
-
import TimeoutError from '../helpers/timeout-error'
|
|
6
5
|
|
|
7
6
|
describe('CAPI response', () => {
|
|
8
7
|
describe('Content ID', () => {
|
|
@@ -70,7 +69,7 @@ describe('CAPI response', () => {
|
|
|
70
69
|
})
|
|
71
70
|
|
|
72
71
|
describe('liveBlogPosts', () => {
|
|
73
|
-
test('returns a resolved array of the articles contained
|
|
72
|
+
test('returns a resolved array of the articles contained', async () => {
|
|
74
73
|
const liveBlogPackage = cloneDeep({
|
|
75
74
|
...baseCapiObject,
|
|
76
75
|
type: 'http://www.ft.com/ontology/content/LiveBlogPackage',
|
|
@@ -81,14 +80,14 @@ describe('CAPI response', () => {
|
|
|
81
80
|
'http://api.ft.com/content/000000000-0000-0000-0000-000000000000',
|
|
82
81
|
},
|
|
83
82
|
{
|
|
84
|
-
id: 'http://api.ft.com/things/000000000-0000-0000-0000-
|
|
83
|
+
id: 'http://api.ft.com/things/000000000-0000-0000-0000-000000000001',
|
|
85
84
|
apiUrl:
|
|
86
|
-
'http://api.ft.com/content/000000000-0000-0000-0000-
|
|
85
|
+
'http://api.ft.com/content/000000000-0000-0000-0000-000000000001',
|
|
87
86
|
},
|
|
88
87
|
{
|
|
89
|
-
id: 'http://api.ft.com/things/000000000-0000-0000-0000-
|
|
88
|
+
id: 'http://api.ft.com/things/000000000-0000-0000-0000-000000000002',
|
|
90
89
|
apiUrl:
|
|
91
|
-
'http://api.ft.com/content/000000000-0000-0000-0000-
|
|
90
|
+
'http://api.ft.com/content/000000000-0000-0000-0000-000000000002',
|
|
92
91
|
},
|
|
93
92
|
],
|
|
94
93
|
})
|
|
@@ -107,6 +106,137 @@ describe('CAPI response', () => {
|
|
|
107
106
|
})
|
|
108
107
|
})
|
|
109
108
|
|
|
109
|
+
describe('liveBlogPostsConnection', () => {
|
|
110
|
+
const generateId = (i: number) =>
|
|
111
|
+
'00000000-0000-0000-0000-0000000000' + String(i).padStart(2, '0')
|
|
112
|
+
|
|
113
|
+
const postCount = 100
|
|
114
|
+
const liveBlogPackage = cloneDeep({
|
|
115
|
+
...baseCapiObject,
|
|
116
|
+
type: 'http://www.ft.com/ontology/content/LiveBlogPackage',
|
|
117
|
+
contains: [...Array(postCount).keys()].map((i) => {
|
|
118
|
+
const id = generateId(postCount - 1 - i)
|
|
119
|
+
return {
|
|
120
|
+
id: 'http://api.ft.com/things/' + id,
|
|
121
|
+
apiUrl: 'http://api.ft.com/content/' + id,
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
})
|
|
125
|
+
const capiResponse = new CapiResponse(liveBlogPackage, context)
|
|
126
|
+
|
|
127
|
+
test('returns all posts when no argument specified', async () => {
|
|
128
|
+
const liveBlogPostsConnection =
|
|
129
|
+
await capiResponse.liveBlogPostsConnection({})
|
|
130
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(postCount)
|
|
131
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
132
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
133
|
+
)
|
|
134
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
135
|
+
expect.objectContaining({
|
|
136
|
+
hasPreviousPage: false,
|
|
137
|
+
hasNextPage: false,
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('returns first x edges', async () => {
|
|
143
|
+
const firstCount = 20
|
|
144
|
+
const liveBlogPostsConnection =
|
|
145
|
+
await capiResponse.liveBlogPostsConnection({ first: firstCount })
|
|
146
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(firstCount)
|
|
147
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
148
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
149
|
+
)
|
|
150
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
151
|
+
expect.objectContaining({
|
|
152
|
+
hasPreviousPage: false,
|
|
153
|
+
hasNextPage: true,
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('returns last x edges', async () => {
|
|
159
|
+
const lastCount = 20
|
|
160
|
+
const liveBlogPostsConnection =
|
|
161
|
+
await capiResponse.liveBlogPostsConnection({ last: lastCount })
|
|
162
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(lastCount)
|
|
163
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
164
|
+
expect(edge.node.id()).toEqual(generateId(i + (postCount - lastCount)))
|
|
165
|
+
)
|
|
166
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
hasPreviousPage: true,
|
|
169
|
+
hasNextPage: false,
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('returns edges after cursor', async () => {
|
|
175
|
+
const firstCount = 20
|
|
176
|
+
const startingLiveBlogPostsConnection =
|
|
177
|
+
await capiResponse.liveBlogPostsConnection({ first: firstCount })
|
|
178
|
+
const cursor = startingLiveBlogPostsConnection.pageInfo.endCursor
|
|
179
|
+
expect(cursor).toBeDefined()
|
|
180
|
+
|
|
181
|
+
const liveBlogPostsConnection =
|
|
182
|
+
await capiResponse.liveBlogPostsConnection({
|
|
183
|
+
first: firstCount,
|
|
184
|
+
after: cursor,
|
|
185
|
+
})
|
|
186
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(firstCount)
|
|
187
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
188
|
+
expect(edge.node.id()).toEqual(generateId(i + firstCount))
|
|
189
|
+
)
|
|
190
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
hasPreviousPage: true,
|
|
193
|
+
hasNextPage: true,
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('returns edges before cursor', async () => {
|
|
199
|
+
const lastCount = 20
|
|
200
|
+
const endingLiveBlogPostsConnection =
|
|
201
|
+
await capiResponse.liveBlogPostsConnection({ last: lastCount })
|
|
202
|
+
const cursor = endingLiveBlogPostsConnection.pageInfo.startCursor
|
|
203
|
+
expect(cursor).toBeDefined()
|
|
204
|
+
|
|
205
|
+
const liveBlogPostsConnection =
|
|
206
|
+
await capiResponse.liveBlogPostsConnection({
|
|
207
|
+
last: lastCount,
|
|
208
|
+
before: cursor,
|
|
209
|
+
})
|
|
210
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(lastCount)
|
|
211
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
212
|
+
expect(edge.node.id()).toEqual(
|
|
213
|
+
generateId(i + (postCount - lastCount * 2))
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
217
|
+
expect.objectContaining({
|
|
218
|
+
hasPreviousPage: true,
|
|
219
|
+
hasNextPage: true,
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('returns all edges when first is greater than total posts', async () => {
|
|
225
|
+
const liveBlogPostsConnection =
|
|
226
|
+
await capiResponse.liveBlogPostsConnection({ first: postCount + 1 })
|
|
227
|
+
expect(liveBlogPostsConnection.edges).toHaveLength(postCount)
|
|
228
|
+
liveBlogPostsConnection.edges.forEach((edge, i) =>
|
|
229
|
+
expect(edge.node.id()).toEqual(generateId(i))
|
|
230
|
+
)
|
|
231
|
+
expect(liveBlogPostsConnection.pageInfo).toEqual(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
hasPreviousPage: false,
|
|
234
|
+
hasNextPage: false,
|
|
235
|
+
})
|
|
236
|
+
)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
110
240
|
describe('Partner content type articles', () => {
|
|
111
241
|
test('should have `isPartnerContent` set correctly as `true` when correct publication id is set', () => {
|
|
112
242
|
const article = cloneDeep({
|
|
@@ -137,7 +267,7 @@ describe('CAPI response', () => {
|
|
|
137
267
|
|
|
138
268
|
const timingOutContext = cloneDeep(context)
|
|
139
269
|
timingOutContext.dataSources.capi.getPerson = () =>
|
|
140
|
-
Promise.reject(new TimeoutError
|
|
270
|
+
Promise.reject(new DOMException(undefined, 'TimeoutError'))
|
|
141
271
|
const capiResponse = new CapiResponse(article, timingOutContext)
|
|
142
272
|
|
|
143
273
|
const authors = await capiResponse.authors()
|