@financial-times/cp-content-pipeline-schema 2.7.0 → 2.9.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 +24 -0
- package/lib/datasources/capi.d.ts +1 -1
- package/lib/datasources/capi.js +14 -39
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/instrumented.d.ts +4 -1
- package/lib/datasources/instrumented.js +16 -16
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/datasources/origami-image.d.ts +1 -1
- package/lib/datasources/origami-image.js +7 -21
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/twitter.d.ts +1 -1
- package/lib/datasources/twitter.js +7 -21
- package/lib/datasources/twitter.js.map +1 -1
- package/lib/generated/index.d.ts +24 -0
- package/lib/model/CapiResponse.d.ts +2 -0
- package/lib/model/CapiResponse.js +40 -4
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Concept.d.ts +0 -2
- package/lib/model/Concept.js +1 -57
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Concept.test.js +0 -40
- package/lib/model/Concept.test.js.map +1 -1
- package/lib/model/Image.js +8 -3
- package/lib/model/Image.js.map +1 -1
- package/lib/model/Person.d.ts +21 -0
- package/lib/model/Person.js +106 -0
- package/lib/model/Person.js.map +1 -0
- package/lib/model/Person.test.d.ts +1 -0
- package/lib/model/Person.test.js +96 -0
- package/lib/model/Person.test.js.map +1 -0
- package/lib/model/Topper.d.ts +2 -1
- package/lib/model/Topper.js +18 -16
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/Topper.test.js +29 -0
- package/lib/model/Topper.test.js.map +1 -1
- package/lib/model/schemas/capi/base-schema.d.ts +3 -0
- package/lib/model/schemas/capi/base-schema.js +1 -0
- package/lib/model/schemas/capi/base-schema.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.js +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js +7 -7
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js.map +1 -1
- package/lib/resolvers/content-tree/references/Flourish.js +7 -2
- package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
- package/lib/resolvers/content-tree/references/RawImage.js +7 -2
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/Recommended.js +1 -1
- package/lib/resolvers/content-tree/references/Recommended.js.map +1 -1
- package/lib/resolvers/content-tree/references/Tweet.js +7 -2
- package/lib/resolvers/content-tree/references/Tweet.js.map +1 -1
- package/lib/resolvers/content-tree/references/Video.js +15 -2
- package/lib/resolvers/content-tree/references/Video.js.map +1 -1
- package/lib/resolvers/content.d.ts +1 -0
- package/lib/resolvers/content.js +1 -0
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/core.js +16 -1
- package/lib/resolvers/core.js.map +1 -1
- package/lib/resolvers/index.d.ts +9 -3
- package/lib/resolvers/index.js +2 -0
- package/lib/resolvers/index.js.map +1 -1
- package/lib/resolvers/person.d.ts +8 -0
- package/lib/resolvers/person.js +11 -0
- package/lib/resolvers/person.js.map +1 -0
- package/lib/resolvers/topper.d.ts +3 -3
- package/package.json +5 -2
- package/queries/article.graphql +9 -0
- package/src/datasources/capi.ts +16 -44
- package/src/datasources/instrumented.ts +29 -31
- package/src/datasources/origami-image.ts +11 -25
- package/src/datasources/twitter.ts +10 -24
- package/src/generated/index.ts +28 -0
- package/src/model/CapiResponse.ts +51 -6
- package/src/model/Concept.test.ts +0 -49
- package/src/model/Concept.ts +1 -32
- package/src/model/Image.ts +9 -4
- package/src/model/Person.test.ts +110 -0
- package/src/model/Person.ts +79 -0
- package/src/model/Topper.test.ts +37 -0
- package/src/model/Topper.ts +18 -23
- package/src/model/schemas/capi/base-schema.ts +1 -0
- package/src/resolvers/content-tree/bodyXMLToTree.test.ts +7 -7
- package/src/resolvers/content-tree/bodyXMLToTree.ts +1 -1
- package/src/resolvers/content-tree/references/Flourish.ts +7 -2
- package/src/resolvers/content-tree/references/RawImage.ts +7 -2
- package/src/resolvers/content-tree/references/Recommended.ts +1 -1
- package/src/resolvers/content-tree/references/Tweet.ts +7 -2
- package/src/resolvers/content-tree/references/Video.ts +18 -4
- package/src/resolvers/content.ts +1 -0
- package/src/resolvers/core.ts +18 -1
- package/src/resolvers/index.ts +2 -0
- package/src/resolvers/person.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +1 -0
- package/typedefs/person.graphql +5 -0
- package/typedefs/topper.graphql +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/resolvers/core.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAmB;AACnB,gDAAuB;AAEvB,wDAAoD;
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/resolvers/core.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAmB;AACnB,gDAAuB;AAEvB,wDAAoD;AACpD,2DAAqE;AAErE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,YAAE,CAAC,YAAY,CAAC,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,OAAO,CAAC,CACxE,CAAA;AAEY,QAAA,OAAO,GAAG,WAAW,CAAC,OAAO,CAAA;AAE1C,MAAM,SAAS,GAAG;IAChB,KAAK,EAAE;QACL,OAAO,EAAE,GAAG,EAAE,CAAC,eAAO;QACtB,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO;YAC5B,IAAI;gBACF,OAAO,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;aAC5D;YAAC,OAAO,KAAK,EAAE;gBACd,IACE,KAAK,YAAY,kBAAS;oBAC1B,KAAK,CAAC,IAAI,CAAC,kBAAkB,KAAK,GAAG,EACrC;oBACA,MAAM,IAAI,kBAAS,CAAC;wBAClB,IAAI,EAAE,mBAAmB;wBACzB,OAAO,EAAE,WAAW,IAAI,CAAC,IAAI,2BAA2B;wBACxD,UAAU,EAAE,GAAG;wBACf,gBAAgB,EAAE,CAAC,QAAQ,CAAC;qBAC7B,CAAC,CAAA;iBACH;gBAED,MAAM,KAAK,CAAA;aACZ;QACH,CAAC;QACD,eAAe,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO;YACrC,OAAO,2BAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC;KACF;IACD,QAAQ,EAAE;QACR,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC/C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,MAAM,CAC9C,qDAAqD,IAAI,CAAC,IAAI,EAAE,CACjE,CAAA;YAED,IAAI,MAAM,EAAE;gBACV,OAAO,qBAAqB,IAAI,CAAC,IAAI,EAAE,CAAA;aACxC;iBAAM,IAAI,MAAM,KAAK,KAAK,EAAE;gBAC3B,OAAO,WAAW,IAAI,CAAC,IAAI,SAAS,CAAA;aACrC;iBAAM;gBACL,OAAO,iBAAiB,CAAA;aACzB;QACH,CAAC;KACF;CAIF,CAAA;AAED,kBAAe,SAAS,CAAA"}
|
package/lib/resolvers/index.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
declare const resolvers: {
|
|
2
|
+
Person: {
|
|
3
|
+
headshot: (parent: import("../model/Person").Person) => Promise<string | null>;
|
|
4
|
+
prefLabel: (parent: import("../model/Person").Person) => string;
|
|
5
|
+
streamPage: (parent: import("../model/Person").Person) => string;
|
|
6
|
+
};
|
|
2
7
|
Topper: {
|
|
3
8
|
__resolveType: import("../generated").TypeResolveFn<"DeepPortraitTopper" | "DeepLandscapeTopper" | "SplitTextTopper" | "FullBleedTopper" | "PodcastTopper" | "OpinionTopper" | "BrandedTopper" | "BasicTopper", import("../model/Topper").Topper, import("..").QueryContext>;
|
|
4
9
|
backgroundBox: import("../generated").Resolver<import("../generated").Maybe<import("../generated").ResolverTypeWrapper<boolean>>, import("../model/Topper").Topper, import("..").QueryContext, {}>;
|
|
@@ -23,11 +28,11 @@ declare const resolvers: {
|
|
|
23
28
|
genreConcept: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
24
29
|
};
|
|
25
30
|
TopperWithHeadshot: {
|
|
26
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").TopperWithHeadshotHeadshotArgs>) => Promise<string | null
|
|
31
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").TopperWithHeadshotHeadshotArgs>) => string | Promise<string | null> | null;
|
|
27
32
|
};
|
|
28
33
|
PodcastTopper: {
|
|
29
34
|
brandConcept: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
30
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").PodcastTopperHeadshotArgs>) => Promise<string | null
|
|
35
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").PodcastTopperHeadshotArgs>) => string | Promise<string | null> | null;
|
|
31
36
|
isLargeHeadline: (topper: import("../model/Topper").Topper) => boolean;
|
|
32
37
|
layout: (topper: import("../model/Topper").Topper) => string;
|
|
33
38
|
columnist: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
@@ -42,7 +47,7 @@ declare const resolvers: {
|
|
|
42
47
|
textShadow: import("../generated").Resolver<import("../generated").Maybe<import("../generated").ResolverTypeWrapper<boolean>>, import("../model/Topper").Topper, import("..").QueryContext, {}>;
|
|
43
48
|
};
|
|
44
49
|
OpinionTopper: {
|
|
45
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").OpinionTopperHeadshotArgs>) => Promise<string | null
|
|
50
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").OpinionTopperHeadshotArgs>) => string | Promise<string | null> | null;
|
|
46
51
|
isLargeHeadline: (topper: import("../model/Topper").Topper) => boolean;
|
|
47
52
|
layout: (topper: import("../model/Topper").Topper) => string;
|
|
48
53
|
columnist: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
@@ -537,6 +542,7 @@ declare const resolvers: {
|
|
|
537
542
|
isOpinion: boolean;
|
|
538
543
|
};
|
|
539
544
|
isPinned: (parent: import("../model/CapiResponse").CapiResponse) => boolean;
|
|
545
|
+
authors: (parent: import("../model/CapiResponse").CapiResponse) => import("../model/Person").Person[] | null;
|
|
540
546
|
__resolveType?: import("../generated").TypeResolveFn<"Article" | "Placeholder" | "Video" | "Audio" | "LiveBlogPackage" | "LiveBlogPost" | "ContentPackage", import("../model/CapiResponse").CapiResponse, import("..").QueryContext> | undefined;
|
|
541
547
|
accessLevel: import("../generated").Resolver<import("../generated").Maybe<import("../generated").ResolverTypeWrapper<"premium" | "subscribed" | "registered" | "free">>, import("../model/CapiResponse").CapiResponse, import("..").QueryContext, {}>;
|
|
542
548
|
altStandfirst: import("../generated").Resolver<import("../generated").Maybe<import("../generated").ResolverTypeWrapper<import("../generated").AltStandfirst>>, import("../model/CapiResponse").CapiResponse, import("..").QueryContext, {}>;
|
package/lib/resolvers/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const richText_1 = __importDefault(require("./richText"));
|
|
|
14
14
|
const scalars_1 = __importDefault(require("./scalars"));
|
|
15
15
|
const teaser_1 = __importDefault(require("./teaser"));
|
|
16
16
|
const topper_1 = __importDefault(require("./topper"));
|
|
17
|
+
const person_1 = __importDefault(require("./person"));
|
|
17
18
|
const references_1 = require("./content-tree/references");
|
|
18
19
|
const resolvers = {
|
|
19
20
|
...concept_1.default,
|
|
@@ -28,6 +29,7 @@ const resolvers = {
|
|
|
28
29
|
...scalars_1.default,
|
|
29
30
|
...teaser_1.default,
|
|
30
31
|
...topper_1.default,
|
|
32
|
+
...person_1.default,
|
|
31
33
|
};
|
|
32
34
|
exports.default = resolvers;
|
|
33
35
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":";;;;;AAAA,wDAA8C;AAC9C,wDAA8C;AAC9C,kDAAwC;AACxC,oDAA0C;AAC1C,kDAAwC;AACxC,4DAAiD;AACjD,wDAA8C;AAC9C,0DAAgD;AAChD,wDAA8C;AAC9C,sDAA4C;AAC5C,sDAA4C;AAC5C,0DAAmE;AAGnE,MAAM,SAAS,GAAG;IAChB,GAAG,iBAAO;IACV,GAAG,iBAAO;IACV,GAAG,cAAI;IACP,GAAG,eAAK;IACR,GAAG,cAAI;IACP,GAAG,mBAAQ;IACX,GAAG,iBAAO;IACV,GAAG,sBAAU;IACb,GAAG,kBAAQ;IACX,GAAG,iBAAO;IACV,GAAG,gBAAM;IACT,GAAG,gBAAM;CACU,CAAA;AAErB,kBAAe,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":";;;;;AAAA,wDAA8C;AAC9C,wDAA8C;AAC9C,kDAAwC;AACxC,oDAA0C;AAC1C,kDAAwC;AACxC,4DAAiD;AACjD,wDAA8C;AAC9C,0DAAgD;AAChD,wDAA8C;AAC9C,sDAA4C;AAC5C,sDAA4C;AAC5C,sDAA4C;AAC5C,0DAAmE;AAGnE,MAAM,SAAS,GAAG;IAChB,GAAG,iBAAO;IACV,GAAG,iBAAO;IACV,GAAG,cAAI;IACP,GAAG,eAAK;IACR,GAAG,cAAI;IACP,GAAG,mBAAQ;IACX,GAAG,iBAAO;IACV,GAAG,sBAAU;IACb,GAAG,kBAAQ;IACX,GAAG,iBAAO;IACV,GAAG,gBAAM;IACT,GAAG,gBAAM;IACT,GAAG,gBAAM;CACU,CAAA;AAErB,kBAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const resolvers: {
|
|
2
|
+
Person: {
|
|
3
|
+
headshot: (parent: import("../model/Person").Person) => Promise<string | null>;
|
|
4
|
+
prefLabel: (parent: import("../model/Person").Person) => string;
|
|
5
|
+
streamPage: (parent: import("../model/Person").Person) => string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export default resolvers;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const resolvers = {
|
|
4
|
+
Person: {
|
|
5
|
+
headshot: (parent) => parent.headshot(),
|
|
6
|
+
prefLabel: (parent) => parent.prefLabel(),
|
|
7
|
+
streamPage: (parent) => parent.streamPage(),
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
exports.default = resolvers;
|
|
11
|
+
//# sourceMappingURL=person.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person.js","sourceRoot":"","sources":["../../src/resolvers/person.ts"],"names":[],"mappings":";;AACA,MAAM,SAAS,GAAG;IAChB,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;QACzC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE;KAC5C;CAGF,CAAA;AAED,kBAAe,SAAS,CAAA"}
|
|
@@ -23,11 +23,11 @@ declare const resolvers: {
|
|
|
23
23
|
genreConcept: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
24
24
|
};
|
|
25
25
|
TopperWithHeadshot: {
|
|
26
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").TopperWithHeadshotHeadshotArgs>) => Promise<string | null
|
|
26
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").TopperWithHeadshotHeadshotArgs>) => string | Promise<string | null> | null;
|
|
27
27
|
};
|
|
28
28
|
PodcastTopper: {
|
|
29
29
|
brandConcept: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
30
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").PodcastTopperHeadshotArgs>) => Promise<string | null
|
|
30
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").PodcastTopperHeadshotArgs>) => string | Promise<string | null> | null;
|
|
31
31
|
isLargeHeadline: (topper: import("../model/Topper").Topper) => boolean;
|
|
32
32
|
layout: (topper: import("../model/Topper").Topper) => string;
|
|
33
33
|
columnist: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
|
@@ -42,7 +42,7 @@ declare const resolvers: {
|
|
|
42
42
|
textShadow: import("../generated").Resolver<import("../generated").Maybe<import("../generated").ResolverTypeWrapper<boolean>>, import("../model/Topper").Topper, import("..").QueryContext, {}>;
|
|
43
43
|
};
|
|
44
44
|
OpinionTopper: {
|
|
45
|
-
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").OpinionTopperHeadshotArgs>) => Promise<string | null
|
|
45
|
+
headshot: (topper: import("../model/Topper").Topper, args: Partial<import("../generated").OpinionTopperHeadshotArgs>) => string | Promise<string | null> | null;
|
|
46
46
|
isLargeHeadline: (topper: import("../model/Topper").Topper) => boolean;
|
|
47
47
|
layout: (topper: import("../model/Topper").Topper) => string;
|
|
48
48
|
columnist: (topper: import("../model/Topper").Topper) => import("../model/Concept").Concept | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@financial-times/cp-content-pipeline-schema",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@apollo/datasource-rest": "^6.2.2",
|
|
22
22
|
"@apollo/utils.keyvaluecache": "^1.0.1",
|
|
23
|
-
"@dotcom-reliability-kit/
|
|
23
|
+
"@dotcom-reliability-kit/fetch-error-handler": "^0.2.3",
|
|
24
24
|
"@dotcom-reliability-kit/log-error": "^2.0.0",
|
|
25
25
|
"@dotcom-reliability-kit/serialize-request": "^2.0.0",
|
|
26
26
|
"@financial-times/n-concept-ids": "^2.1.0",
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
"@types/lodash.sortby": "^4.7.7",
|
|
51
51
|
"type-fest": "^3.13.1"
|
|
52
52
|
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@dotcom-reliability-kit/errors": "^3.1.0"
|
|
55
|
+
},
|
|
53
56
|
"engines": {
|
|
54
57
|
"node": "18.x"
|
|
55
58
|
}
|
package/queries/article.graphql
CHANGED
|
@@ -75,6 +75,12 @@ fragment Intro on RichText {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
fragment Person on Person {
|
|
79
|
+
headshot(dpr: 2, width: 150)
|
|
80
|
+
prefLabel
|
|
81
|
+
streamPage
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
fragment Topper on Topper {
|
|
79
85
|
__typename
|
|
80
86
|
headline
|
|
@@ -590,6 +596,9 @@ fragment ArticleFields on Content {
|
|
|
590
596
|
...Content
|
|
591
597
|
url
|
|
592
598
|
... on LiveBlogPost {
|
|
599
|
+
authors {
|
|
600
|
+
...Person
|
|
601
|
+
}
|
|
593
602
|
isPinned
|
|
594
603
|
indicators {
|
|
595
604
|
...Indicators
|
package/src/datasources/capi.ts
CHANGED
|
@@ -2,10 +2,6 @@ import { CapiResponse } from '../model/CapiResponse'
|
|
|
2
2
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
3
3
|
import { CapiPerson } from '../types/internal-content'
|
|
4
4
|
import { AugmentedRequest } from '@apollo/datasource-rest'
|
|
5
|
-
import {
|
|
6
|
-
OperationalError,
|
|
7
|
-
UpstreamServiceError,
|
|
8
|
-
} from '@dotcom-reliability-kit/errors'
|
|
9
5
|
|
|
10
6
|
const REQUEST_TIMEOUT = 5000 // 5 seconds
|
|
11
7
|
|
|
@@ -19,7 +15,7 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
19
15
|
? parseInt(process.env.PEOPLE_CACHE_TTL)
|
|
20
16
|
: 600 // 10 minutes
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
backendSystemCode = 'up-ica'
|
|
23
19
|
|
|
24
20
|
abortController = new AbortController()
|
|
25
21
|
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
@@ -41,47 +37,23 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
41
37
|
uuid: string,
|
|
42
38
|
packageContainer?: CapiResponse
|
|
43
39
|
): Promise<CapiResponse> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.timeout = undefined
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return CapiResponse.fromJSON(content, this.context, packageContainer)
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
59
|
-
throw new UpstreamServiceError({
|
|
60
|
-
code: 'CAPI_DATASOURCE_TIMEOUT',
|
|
61
|
-
message: `Request to Internal Content API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
62
|
-
statusCode: 408,
|
|
63
|
-
relatesToSystems: this.backendSystemCodes,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
throw error
|
|
40
|
+
const content = await this.get(`internalcontent/${uuid}`, {
|
|
41
|
+
cacheOptions: { ttl: this.articleCacheTTL },
|
|
42
|
+
})
|
|
43
|
+
this.context.contentRequestedOnce = true
|
|
44
|
+
this.calls.push(uuid)
|
|
45
|
+
|
|
46
|
+
if (this.timeout) {
|
|
47
|
+
clearTimeout(this.timeout)
|
|
48
|
+
this.timeout = undefined
|
|
68
49
|
}
|
|
69
|
-
}
|
|
70
50
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return await this.get(`people/${uuid}`, {
|
|
74
|
-
cacheOptions: { ttl: this.peopleCacheTTL },
|
|
75
|
-
})
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (error instanceof Error) {
|
|
78
|
-
throw new OperationalError({
|
|
79
|
-
cause: error,
|
|
80
|
-
code: 'CAPI_DATASOURCE_PERSON_ERROR',
|
|
81
|
-
})
|
|
82
|
-
}
|
|
51
|
+
return CapiResponse.fromJSON(content, this.context, packageContainer)
|
|
52
|
+
}
|
|
83
53
|
|
|
84
|
-
|
|
85
|
-
}
|
|
54
|
+
getPerson(uuid: string): Promise<CapiPerson> {
|
|
55
|
+
return this.get(`people/${uuid}`, {
|
|
56
|
+
cacheOptions: { ttl: this.peopleCacheTTL },
|
|
57
|
+
})
|
|
86
58
|
}
|
|
87
59
|
}
|
|
@@ -6,19 +6,22 @@ import {
|
|
|
6
6
|
AugmentedRequest,
|
|
7
7
|
} from '@apollo/datasource-rest'
|
|
8
8
|
import { PrefixingKeyValueCache } from '@apollo/utils.keyvaluecache'
|
|
9
|
-
import {
|
|
9
|
+
import { createFetchErrorHandler } from '@dotcom-reliability-kit/fetch-error-handler'
|
|
10
|
+
import type { FetchErrorHandler } from '@dotcom-reliability-kit/fetch-error-handler/lib/create-handler'
|
|
10
11
|
import { QueryContext } from '..'
|
|
11
12
|
import { isContextableCache } from '../types/cache'
|
|
12
13
|
import { BaseDataSource, BaseDataSourceOptions } from './base'
|
|
14
|
+
import { BaseError } from '@dotcom-reliability-kit/errors'
|
|
13
15
|
|
|
14
16
|
export class InstrumentedRESTDataSource
|
|
15
17
|
extends RESTDataSource
|
|
16
18
|
implements BaseDataSource
|
|
17
19
|
{
|
|
18
20
|
startTime?: bigint
|
|
19
|
-
|
|
21
|
+
backendSystemCode: string | undefined
|
|
20
22
|
context: QueryContext
|
|
21
23
|
calls: string[] = []
|
|
24
|
+
errorHandler: FetchErrorHandler
|
|
22
25
|
|
|
23
26
|
constructor({ cache, context }: BaseDataSourceOptions) {
|
|
24
27
|
const wrappedCache = new PrefixingKeyValueCache(
|
|
@@ -28,6 +31,11 @@ export class InstrumentedRESTDataSource
|
|
|
28
31
|
|
|
29
32
|
super({
|
|
30
33
|
cache: wrappedCache,
|
|
34
|
+
fetch: (url, init) => this.errorHandler(global.fetch(url, init)),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
this.errorHandler = createFetchErrorHandler({
|
|
38
|
+
upstreamSystemCode: this.backendSystemCode,
|
|
31
39
|
})
|
|
32
40
|
|
|
33
41
|
// okay _now_ we can use `this`
|
|
@@ -59,6 +67,18 @@ export class InstrumentedRESTDataSource
|
|
|
59
67
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
60
68
|
async throwIfResponseIsError() {}
|
|
61
69
|
|
|
70
|
+
logResponseMetrics(status: number, duration: bigint): void {
|
|
71
|
+
this.context.metrics?.count(
|
|
72
|
+
`graphql.datasource.${this.constructor.name}.response.${status}.count`,
|
|
73
|
+
1
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
this.context.metrics?.count(
|
|
77
|
+
`graphql.datasource.${this.constructor.name}.response.${status}.time`,
|
|
78
|
+
Number(duration)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
async fetch<TResult>(
|
|
63
83
|
path: string,
|
|
64
84
|
incomingRequest?: DataSourceRequest<CacheOptions>
|
|
@@ -74,40 +94,18 @@ export class InstrumentedRESTDataSource
|
|
|
74
94
|
const result = await super.fetch<TResult>(path, incomingRequest)
|
|
75
95
|
const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6)
|
|
76
96
|
|
|
77
|
-
this.
|
|
78
|
-
`graphql.datasource.${this.constructor.name}.response.${result.response.status}.count`,
|
|
79
|
-
1
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
this.context.metrics?.count(
|
|
83
|
-
`graphql.datasource.${this.constructor.name}.response.${result.response.status}.time`,
|
|
84
|
-
Number(duration)
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
if (!result.response.ok) {
|
|
88
|
-
throw new UpstreamServiceError({
|
|
89
|
-
message: `${result.response.status}: ${result.response.statusText} from ${this.constructor.name}`,
|
|
90
|
-
statusCode: result.response.status,
|
|
91
|
-
relatesToSystems: this.backendSystemCodes,
|
|
92
|
-
url: result.response.url,
|
|
93
|
-
body: result.parsedBody,
|
|
94
|
-
})
|
|
95
|
-
}
|
|
97
|
+
this.logResponseMetrics(result.response.status, duration)
|
|
96
98
|
|
|
97
99
|
return result
|
|
98
100
|
} catch (error) {
|
|
99
|
-
if (error instanceof
|
|
101
|
+
if (error instanceof BaseError) {
|
|
102
|
+
const status =
|
|
103
|
+
error.code === 'FETCH_ABORT_ERROR'
|
|
104
|
+
? 408
|
|
105
|
+
: Number(error.data.upstreamStatusCode ?? 0)
|
|
100
106
|
const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6)
|
|
101
107
|
|
|
102
|
-
this.
|
|
103
|
-
`graphql.datasource.${this.constructor.name}.response.408.count`,
|
|
104
|
-
1
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
this.context.metrics?.count(
|
|
108
|
-
`graphql.datasource.${this.constructor.name}.response.408.time`,
|
|
109
|
-
Number(duration)
|
|
110
|
-
)
|
|
108
|
+
this.logResponseMetrics(status, duration)
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
throw error
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { UpstreamServiceError } from '@dotcom-reliability-kit/errors'
|
|
2
1
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
3
2
|
import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
4
3
|
|
|
@@ -6,7 +5,7 @@ const REQUEST_TIMEOUT = 5000 // 5 seconds
|
|
|
6
5
|
|
|
7
6
|
export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
8
7
|
baseURL = 'https://www.ft.com/__origami/service/image/v2/'
|
|
9
|
-
|
|
8
|
+
backendSystemCode = 'origami-image-service-v2'
|
|
10
9
|
|
|
11
10
|
abortController = new AbortController()
|
|
12
11
|
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
@@ -32,29 +31,16 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
32
31
|
async getImageMetadata(
|
|
33
32
|
url: string
|
|
34
33
|
): Promise<{ width: number; height: number }> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.timeout = undefined
|
|
45
|
-
}
|
|
46
|
-
return imageMetadata
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
49
|
-
throw new UpstreamServiceError({
|
|
50
|
-
code: 'ORIGAMI_DATASOURCE_TIMEOUT',
|
|
51
|
-
message: `Request to Image Service took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
52
|
-
statusCode: 408,
|
|
53
|
-
relatesToSystems: this.backendSystemCodes,
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
throw error
|
|
34
|
+
const imageMetadata = await this.get(
|
|
35
|
+
`images/metadata/${encodeURIComponent(url)}?source=next`
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
this.calls.push(url)
|
|
39
|
+
|
|
40
|
+
if (this.timeout) {
|
|
41
|
+
clearTimeout(this.timeout)
|
|
42
|
+
this.timeout = undefined
|
|
58
43
|
}
|
|
44
|
+
return imageMetadata
|
|
59
45
|
}
|
|
60
46
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest'
|
|
2
|
-
import { UpstreamServiceError } from '@dotcom-reliability-kit/errors'
|
|
3
2
|
import { InstrumentedRESTDataSource } from './instrumented'
|
|
4
3
|
|
|
5
4
|
const REQUEST_TIMEOUT = 5000 // 5 seconds
|
|
6
5
|
export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
7
6
|
baseURL = 'https://publish.twitter.com'
|
|
8
|
-
|
|
7
|
+
backendSystemCode = 'twitter-oembed-api'
|
|
9
8
|
|
|
10
9
|
abortController = new AbortController()
|
|
11
10
|
timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
|
@@ -21,29 +20,16 @@ export class TwitterDataSource extends InstrumentedRESTDataSource {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
async getTweet(tweetUrl: string) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.timeout = undefined
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return tweet
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
37
|
-
throw new UpstreamServiceError({
|
|
38
|
-
code: 'TWITTER_DATASOURCE_TIMEOUT',
|
|
39
|
-
message: `Request to Twitter API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
|
|
40
|
-
statusCode: 408,
|
|
41
|
-
relatesToSystems: this.backendSystemCodes,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw error
|
|
23
|
+
const tweet = await this.get(`oembed?url=${tweetUrl}&omit_script=true`)
|
|
24
|
+
|
|
25
|
+
this.calls.push(tweetUrl)
|
|
26
|
+
|
|
27
|
+
if (this.timeout) {
|
|
28
|
+
clearTimeout(this.timeout)
|
|
29
|
+
this.timeout = undefined
|
|
46
30
|
}
|
|
31
|
+
|
|
32
|
+
return tweet
|
|
47
33
|
}
|
|
48
34
|
|
|
49
35
|
cacheOptionsFor(): CacheOptions {
|
package/src/generated/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
|
|
2
2
|
import type { Concept as ConceptModel } from '../model/Concept';
|
|
3
|
+
import type { Person as PersonModel } from '../model/Person';
|
|
3
4
|
import type { CapiResponse } from '../model/CapiResponse';
|
|
4
5
|
import type { Image as ImageModel } from '../model/Image';
|
|
5
6
|
import type { Clip as ClipModel } from '../model/Clip';
|
|
@@ -990,6 +991,7 @@ export type LiveBlogPost = Content & {
|
|
|
990
991
|
readonly altTitle?: Maybe<AltTitle>;
|
|
991
992
|
/** An array of concepts related to the article, eg. organisations or topics. */
|
|
992
993
|
readonly annotations?: Maybe<ReadonlyArray<Maybe<Concept>>>;
|
|
994
|
+
readonly authors?: Maybe<ReadonlyArray<Maybe<Person>>>;
|
|
993
995
|
/** An abstract syntax tree of the article content. */
|
|
994
996
|
readonly body?: Maybe<RichText>;
|
|
995
997
|
/** The raw string of the XML as returned from ContentAPI. */
|
|
@@ -1108,6 +1110,19 @@ export type OpinionTopper = Topper & TopperWithHeadshot & TopperWithTheme & {
|
|
|
1108
1110
|
|
|
1109
1111
|
|
|
1110
1112
|
export type OpinionTopperHeadshotArgs = {
|
|
1113
|
+
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1114
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1115
|
+
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
export type Person = {
|
|
1119
|
+
readonly headshot?: Maybe<Scalars['String']['output']>;
|
|
1120
|
+
readonly prefLabel?: Maybe<Scalars['String']['output']>;
|
|
1121
|
+
readonly streamPage?: Maybe<Scalars['String']['output']>;
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
export type PersonHeadshotArgs = {
|
|
1111
1126
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1112
1127
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1113
1128
|
};
|
|
@@ -1277,6 +1292,7 @@ export type PodcastTopper = Topper & TopperWithBrand & TopperWithHeadshot & Topp
|
|
|
1277
1292
|
|
|
1278
1293
|
export type PodcastTopperHeadshotArgs = {
|
|
1279
1294
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1295
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1280
1296
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1281
1297
|
};
|
|
1282
1298
|
|
|
@@ -1454,6 +1470,7 @@ export type TopperWithHeadshot = {
|
|
|
1454
1470
|
|
|
1455
1471
|
export type TopperWithHeadshotHeadshotArgs = {
|
|
1456
1472
|
dpr?: InputMaybe<Scalars['Int']['input']>;
|
|
1473
|
+
url?: InputMaybe<Scalars['String']['input']>;
|
|
1457
1474
|
width?: InputMaybe<Scalars['Int']['input']>;
|
|
1458
1475
|
};
|
|
1459
1476
|
|
|
@@ -1697,6 +1714,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
1697
1714
|
Mutation: ResolverTypeWrapper<{}>;
|
|
1698
1715
|
OpinionTopper: ResolverTypeWrapper<TopperModel>;
|
|
1699
1716
|
PackageDesign: ResolverTypeWrapper<Scalars['PackageDesign']['output']>;
|
|
1717
|
+
Person: ResolverTypeWrapper<PersonModel>;
|
|
1700
1718
|
Picture: ResolverTypeWrapper<PictureModel>;
|
|
1701
1719
|
PictureFullBleed: ResolverTypeWrapper<PictureModel>;
|
|
1702
1720
|
PictureInline: ResolverTypeWrapper<PictureModel>;
|
|
@@ -1783,6 +1801,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
1783
1801
|
Mutation: {};
|
|
1784
1802
|
OpinionTopper: TopperModel;
|
|
1785
1803
|
PackageDesign: Scalars['PackageDesign']['output'];
|
|
1804
|
+
Person: PersonModel;
|
|
1786
1805
|
Picture: PictureModel;
|
|
1787
1806
|
PictureFullBleed: PictureModel;
|
|
1788
1807
|
PictureInline: PictureModel;
|
|
@@ -2341,6 +2360,7 @@ export type LiveBlogPostResolvers<ContextType = QueryContext, ParentType extends
|
|
|
2341
2360
|
altStandfirst: Resolver<Maybe<ResolversTypes['AltStandfirst']>, ParentType, ContextType>;
|
|
2342
2361
|
altTitle: Resolver<Maybe<ResolversTypes['AltTitle']>, ParentType, ContextType>;
|
|
2343
2362
|
annotations: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Concept']>>>, ParentType, ContextType>;
|
|
2363
|
+
authors: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Person']>>>, ParentType, ContextType>;
|
|
2344
2364
|
body: Resolver<Maybe<ResolversTypes['RichText']>, ParentType, ContextType>;
|
|
2345
2365
|
bodyXML: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2346
2366
|
byline: Resolver<Maybe<ResolversTypes['StructuredContent']>, ParentType, ContextType, Partial<LiveBlogPostBylineArgs>>;
|
|
@@ -2409,6 +2429,13 @@ export interface PackageDesignScalarConfig extends GraphQLScalarTypeConfig<Resol
|
|
|
2409
2429
|
name: 'PackageDesign';
|
|
2410
2430
|
}
|
|
2411
2431
|
|
|
2432
|
+
export type PersonResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person']> = ResolversObject<{
|
|
2433
|
+
headshot: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, Partial<PersonHeadshotArgs>>;
|
|
2434
|
+
prefLabel: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2435
|
+
streamPage: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2436
|
+
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2437
|
+
}>;
|
|
2438
|
+
|
|
2412
2439
|
export type PictureResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Picture'] = ResolversParentTypes['Picture']> = ResolversObject<{
|
|
2413
2440
|
__resolveType?: TypeResolveFn<'PictureFullBleed' | 'PictureInline' | 'PictureStandard', ParentType, ContextType>;
|
|
2414
2441
|
alt: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
@@ -2725,6 +2752,7 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
|
|
|
2725
2752
|
Mutation: MutationResolvers<ContextType>;
|
|
2726
2753
|
OpinionTopper: OpinionTopperResolvers<ContextType>;
|
|
2727
2754
|
PackageDesign: GraphQLScalarType;
|
|
2755
|
+
Person: PersonResolvers<ContextType>;
|
|
2728
2756
|
Picture: PictureResolvers<ContextType>;
|
|
2729
2757
|
PictureFullBleed: PictureFullBleedResolvers<ContextType>;
|
|
2730
2758
|
PictureInline: PictureInlineResolvers<ContextType>;
|