@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/lib/datasources/capi.d.ts +1 -1
  3. package/lib/datasources/capi.js +14 -39
  4. package/lib/datasources/capi.js.map +1 -1
  5. package/lib/datasources/instrumented.d.ts +4 -1
  6. package/lib/datasources/instrumented.js +16 -16
  7. package/lib/datasources/instrumented.js.map +1 -1
  8. package/lib/datasources/origami-image.d.ts +1 -1
  9. package/lib/datasources/origami-image.js +7 -21
  10. package/lib/datasources/origami-image.js.map +1 -1
  11. package/lib/datasources/twitter.d.ts +1 -1
  12. package/lib/datasources/twitter.js +7 -21
  13. package/lib/datasources/twitter.js.map +1 -1
  14. package/lib/generated/index.d.ts +24 -0
  15. package/lib/model/CapiResponse.d.ts +2 -0
  16. package/lib/model/CapiResponse.js +40 -4
  17. package/lib/model/CapiResponse.js.map +1 -1
  18. package/lib/model/Concept.d.ts +0 -2
  19. package/lib/model/Concept.js +1 -57
  20. package/lib/model/Concept.js.map +1 -1
  21. package/lib/model/Concept.test.js +0 -40
  22. package/lib/model/Concept.test.js.map +1 -1
  23. package/lib/model/Image.js +8 -3
  24. package/lib/model/Image.js.map +1 -1
  25. package/lib/model/Person.d.ts +21 -0
  26. package/lib/model/Person.js +106 -0
  27. package/lib/model/Person.js.map +1 -0
  28. package/lib/model/Person.test.d.ts +1 -0
  29. package/lib/model/Person.test.js +96 -0
  30. package/lib/model/Person.test.js.map +1 -0
  31. package/lib/model/Topper.d.ts +2 -1
  32. package/lib/model/Topper.js +18 -16
  33. package/lib/model/Topper.js.map +1 -1
  34. package/lib/model/Topper.test.js +29 -0
  35. package/lib/model/Topper.test.js.map +1 -1
  36. package/lib/model/schemas/capi/base-schema.d.ts +3 -0
  37. package/lib/model/schemas/capi/base-schema.js +1 -0
  38. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  39. package/lib/resolvers/content-tree/bodyXMLToTree.js +1 -1
  40. package/lib/resolvers/content-tree/bodyXMLToTree.js.map +1 -1
  41. package/lib/resolvers/content-tree/bodyXMLToTree.test.js +7 -7
  42. package/lib/resolvers/content-tree/bodyXMLToTree.test.js.map +1 -1
  43. package/lib/resolvers/content-tree/references/Flourish.js +7 -2
  44. package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
  45. package/lib/resolvers/content-tree/references/RawImage.js +7 -2
  46. package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
  47. package/lib/resolvers/content-tree/references/Recommended.js +1 -1
  48. package/lib/resolvers/content-tree/references/Recommended.js.map +1 -1
  49. package/lib/resolvers/content-tree/references/Tweet.js +7 -2
  50. package/lib/resolvers/content-tree/references/Tweet.js.map +1 -1
  51. package/lib/resolvers/content-tree/references/Video.js +15 -2
  52. package/lib/resolvers/content-tree/references/Video.js.map +1 -1
  53. package/lib/resolvers/content.d.ts +1 -0
  54. package/lib/resolvers/content.js +1 -0
  55. package/lib/resolvers/content.js.map +1 -1
  56. package/lib/resolvers/core.js +16 -1
  57. package/lib/resolvers/core.js.map +1 -1
  58. package/lib/resolvers/index.d.ts +9 -3
  59. package/lib/resolvers/index.js +2 -0
  60. package/lib/resolvers/index.js.map +1 -1
  61. package/lib/resolvers/person.d.ts +8 -0
  62. package/lib/resolvers/person.js +11 -0
  63. package/lib/resolvers/person.js.map +1 -0
  64. package/lib/resolvers/topper.d.ts +3 -3
  65. package/package.json +5 -2
  66. package/queries/article.graphql +9 -0
  67. package/src/datasources/capi.ts +16 -44
  68. package/src/datasources/instrumented.ts +29 -31
  69. package/src/datasources/origami-image.ts +11 -25
  70. package/src/datasources/twitter.ts +10 -24
  71. package/src/generated/index.ts +28 -0
  72. package/src/model/CapiResponse.ts +51 -6
  73. package/src/model/Concept.test.ts +0 -49
  74. package/src/model/Concept.ts +1 -32
  75. package/src/model/Image.ts +9 -4
  76. package/src/model/Person.test.ts +110 -0
  77. package/src/model/Person.ts +79 -0
  78. package/src/model/Topper.test.ts +37 -0
  79. package/src/model/Topper.ts +18 -23
  80. package/src/model/schemas/capi/base-schema.ts +1 -0
  81. package/src/resolvers/content-tree/bodyXMLToTree.test.ts +7 -7
  82. package/src/resolvers/content-tree/bodyXMLToTree.ts +1 -1
  83. package/src/resolvers/content-tree/references/Flourish.ts +7 -2
  84. package/src/resolvers/content-tree/references/RawImage.ts +7 -2
  85. package/src/resolvers/content-tree/references/Recommended.ts +1 -1
  86. package/src/resolvers/content-tree/references/Tweet.ts +7 -2
  87. package/src/resolvers/content-tree/references/Video.ts +18 -4
  88. package/src/resolvers/content.ts +1 -0
  89. package/src/resolvers/core.ts +18 -1
  90. package/src/resolvers/index.ts +2 -0
  91. package/src/resolvers/person.ts +12 -0
  92. package/tsconfig.tsbuildinfo +1 -1
  93. package/typedefs/content.graphql +1 -0
  94. package/typedefs/person.graphql +5 -0
  95. package/typedefs/topper.graphql +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.9.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.8.0...cp-content-pipeline-schema-v2.9.0) (2024-05-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * construct OperationalError at the call site for datasources ([f07cb89](https://github.com/Financial-Times/cp-content-pipeline/commit/f07cb8933f63ebfbe96d9ccadb370c275bce8b43))
9
+ * use fetch-error-handler and centralize datasource error handling ([4c5d0d6](https://github.com/Financial-Times/cp-content-pipeline/commit/4c5d0d66dd78cb6a5db316759a2c8788b1d30576))
10
+ * use native fetch ([58e682b](https://github.com/Financial-Times/cp-content-pipeline/commit/58e682b149d65e6ff55a0f828f8e8f7e6f06c200))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * only wrap getPerson errors in OperationalError at call site ([05b1c51](https://github.com/Financial-Times/cp-content-pipeline/commit/05b1c51ddf6d63f73cacde895faa256cff8a676f))
16
+ * return null for containedIn on error instead of throwing ([a7e96f3](https://github.com/Financial-Times/cp-content-pipeline/commit/a7e96f3e180e994c37482e93e1e396a612f7db0f))
17
+ * use logger.warn for RECOVERABLE_ERROR ([d7f44a5](https://github.com/Financial-Times/cp-content-pipeline/commit/d7f44a526e300afad82f4296014b61c1e19a0a9c))
18
+ * use return await to let it catch promise rejections ([bc08819](https://github.com/Financial-Times/cp-content-pipeline/commit/bc088199263d92023ba03b71fb6497aa10eebe3f))
19
+
20
+ ## [2.8.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.7.0...cp-content-pipeline-schema-v2.8.0) (2024-04-29)
21
+
22
+
23
+ ### Features
24
+
25
+ * clickable bylines and headshots in opinion posts ([9295ce3](https://github.com/Financial-Times/cp-content-pipeline/commit/9295ce31e6e58d59882c1b74232ed5c7338f001d))
26
+
3
27
  ## [2.7.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v2.6.2...cp-content-pipeline-schema-v2.7.0) (2024-04-26)
4
28
 
5
29
 
@@ -7,7 +7,7 @@ export declare class CapiDataSource extends InstrumentedRESTDataSource {
7
7
  capiKey: string;
8
8
  articleCacheTTL: number;
9
9
  peopleCacheTTL: number;
10
- backendSystemCodes: string[];
10
+ backendSystemCode: string;
11
11
  abortController: AbortController;
12
12
  timeout: ReturnType<typeof setTimeout> | undefined;
13
13
  nextNotificationLink?: string;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CapiDataSource = void 0;
4
4
  const CapiResponse_1 = require("../model/CapiResponse");
5
5
  const instrumented_1 = require("./instrumented");
6
- const errors_1 = require("@dotcom-reliability-kit/errors");
7
6
  const REQUEST_TIMEOUT = 5000; // 5 seconds
8
7
  class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
9
8
  constructor() {
@@ -16,7 +15,7 @@ class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
16
15
  this.peopleCacheTTL = process.env.PEOPLE_CACHE_TTL
17
16
  ? parseInt(process.env.PEOPLE_CACHE_TTL)
18
17
  : 600; // 10 minutes
19
- this.backendSystemCodes = ['up-ica'];
18
+ this.backendSystemCode = 'up-ica';
20
19
  this.abortController = new AbortController();
21
20
  this.timeout = undefined;
22
21
  }
@@ -27,45 +26,21 @@ class CapiDataSource extends instrumented_1.InstrumentedRESTDataSource {
27
26
  this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
28
27
  }
29
28
  async getContent(uuid, packageContainer) {
30
- try {
31
- const content = await this.get(`internalcontent/${uuid}`, {
32
- cacheOptions: { ttl: this.articleCacheTTL },
33
- });
34
- this.context.contentRequestedOnce = true;
35
- this.calls.push(uuid);
36
- if (this.timeout) {
37
- clearTimeout(this.timeout);
38
- this.timeout = undefined;
39
- }
40
- return CapiResponse_1.CapiResponse.fromJSON(content, this.context, packageContainer);
41
- }
42
- catch (error) {
43
- if (error instanceof Error && error.name === 'AbortError') {
44
- throw new errors_1.UpstreamServiceError({
45
- code: 'CAPI_DATASOURCE_TIMEOUT',
46
- message: `Request to Internal Content API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
47
- statusCode: 408,
48
- relatesToSystems: this.backendSystemCodes,
49
- });
50
- }
51
- throw error;
29
+ const content = await this.get(`internalcontent/${uuid}`, {
30
+ cacheOptions: { ttl: this.articleCacheTTL },
31
+ });
32
+ this.context.contentRequestedOnce = true;
33
+ this.calls.push(uuid);
34
+ if (this.timeout) {
35
+ clearTimeout(this.timeout);
36
+ this.timeout = undefined;
52
37
  }
38
+ return CapiResponse_1.CapiResponse.fromJSON(content, this.context, packageContainer);
53
39
  }
54
- async getPerson(uuid) {
55
- try {
56
- return await this.get(`people/${uuid}`, {
57
- cacheOptions: { ttl: this.peopleCacheTTL },
58
- });
59
- }
60
- catch (error) {
61
- if (error instanceof Error) {
62
- throw new errors_1.OperationalError({
63
- cause: error,
64
- code: 'CAPI_DATASOURCE_PERSON_ERROR',
65
- });
66
- }
67
- throw error;
68
- }
40
+ getPerson(uuid) {
41
+ return this.get(`people/${uuid}`, {
42
+ cacheOptions: { ttl: this.peopleCacheTTL },
43
+ });
69
44
  }
70
45
  }
71
46
  exports.CapiDataSource = CapiDataSource;
@@ -1 +1 @@
1
- {"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":";;;AAAA,wDAAoD;AACpD,iDAA2D;AAI3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAa,cAAe,SAAQ,yCAA0B;IAA9D;;QACE,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAA;QACzD,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAA;QACxC,oBAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;YACzC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA,CAAC,UAAU;QACnC,mBAAc,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAC3C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACxC,CAAC,CAAC,GAAG,CAAA,CAAC,aAAa;QAErB,sBAAiB,GAAG,QAAQ,CAAA;QAE5B,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IAsChE,CAAC;IAlCU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,OAAO,CAAA;QAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CACd,IAAY,EACZ,gBAA+B;QAE/B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,EAAE;YACxD,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE;SAC5C,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAA;QACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAErB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QAED,OAAO,2BAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;IACvE,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE;YAChC,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE;SAC3C,CAAC,CAAA;IACJ,CAAC;CACF;AAnDD,wCAmDC"}
@@ -1,15 +1,18 @@
1
1
  import { RESTDataSource, CacheOptions, DataSourceFetchResult, DataSourceRequest, AugmentedRequest } from '@apollo/datasource-rest';
2
+ import type { FetchErrorHandler } from '@dotcom-reliability-kit/fetch-error-handler/lib/create-handler';
2
3
  import { QueryContext } from '..';
3
4
  import { BaseDataSource, BaseDataSourceOptions } from './base';
4
5
  export declare class InstrumentedRESTDataSource extends RESTDataSource implements BaseDataSource {
5
6
  startTime?: bigint;
6
- backendSystemCodes: string[];
7
+ backendSystemCode: string | undefined;
7
8
  context: QueryContext;
8
9
  calls: string[];
10
+ errorHandler: FetchErrorHandler;
9
11
  constructor({ cache, context }: BaseDataSourceOptions);
10
12
  get cachePrefix(): string;
11
13
  protected cacheKeyFor(url: URL): string;
12
14
  willSendRequest(path: string, request: AugmentedRequest): void;
13
15
  throwIfResponseIsError(): Promise<void>;
16
+ logResponseMetrics(status: number, duration: bigint): void;
14
17
  fetch<TResult>(path: string, incomingRequest?: DataSourceRequest<CacheOptions>): Promise<DataSourceFetchResult<TResult>>;
15
18
  }
@@ -3,17 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InstrumentedRESTDataSource = void 0;
4
4
  const datasource_rest_1 = require("@apollo/datasource-rest");
5
5
  const utils_keyvaluecache_1 = require("@apollo/utils.keyvaluecache");
6
- const errors_1 = require("@dotcom-reliability-kit/errors");
6
+ const fetch_error_handler_1 = require("@dotcom-reliability-kit/fetch-error-handler");
7
7
  const cache_1 = require("../types/cache");
8
+ const errors_1 = require("@dotcom-reliability-kit/errors");
8
9
  class InstrumentedRESTDataSource extends datasource_rest_1.RESTDataSource {
9
10
  constructor({ cache, context }) {
10
11
  const wrappedCache = new utils_keyvaluecache_1.PrefixingKeyValueCache((0, cache_1.isContextableCache)(cache) ? cache.withContext(context) : cache, 'placeholder_prefix_if_you_see_this_in_redis_something_is_very_wrong:' // can't use `this` yet to get our prefix
11
12
  );
12
13
  super({
13
14
  cache: wrappedCache,
15
+ fetch: (url, init) => this.errorHandler(global.fetch(url, init)),
14
16
  });
15
- this.backendSystemCodes = [];
16
17
  this.calls = [];
18
+ this.errorHandler = (0, fetch_error_handler_1.createFetchErrorHandler)({
19
+ upstreamSystemCode: this.backendSystemCode,
20
+ });
17
21
  // okay _now_ we can use `this`
18
22
  // also that's a private property so use the [] escape hatch
19
23
  // sorry
@@ -35,30 +39,26 @@ class InstrumentedRESTDataSource extends datasource_rest_1.RESTDataSource {
35
39
  // we implement our own error handling so skip the built-in GraphQLError
36
40
  // eslint-disable-next-line @typescript-eslint/no-empty-function
37
41
  async throwIfResponseIsError() { }
42
+ logResponseMetrics(status, duration) {
43
+ this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${status}.count`, 1);
44
+ this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${status}.time`, Number(duration));
45
+ }
38
46
  async fetch(path, incomingRequest) {
39
47
  const startTime = process.hrtime.bigint();
40
48
  this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.request.count`, 1);
41
49
  try {
42
50
  const result = await super.fetch(path, incomingRequest);
43
51
  const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6);
44
- this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${result.response.status}.count`, 1);
45
- this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.${result.response.status}.time`, Number(duration));
46
- if (!result.response.ok) {
47
- throw new errors_1.UpstreamServiceError({
48
- message: `${result.response.status}: ${result.response.statusText} from ${this.constructor.name}`,
49
- statusCode: result.response.status,
50
- relatesToSystems: this.backendSystemCodes,
51
- url: result.response.url,
52
- body: result.parsedBody,
53
- });
54
- }
52
+ this.logResponseMetrics(result.response.status, duration);
55
53
  return result;
56
54
  }
57
55
  catch (error) {
58
- if (error instanceof Error && error.name === 'AbortError') {
56
+ if (error instanceof errors_1.BaseError) {
57
+ const status = error.code === 'FETCH_ABORT_ERROR'
58
+ ? 408
59
+ : Number(error.data.upstreamStatusCode ?? 0);
59
60
  const duration = (process.hrtime.bigint() - startTime) / BigInt(1e6);
60
- this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.408.count`, 1);
61
- this.context.metrics?.count(`graphql.datasource.${this.constructor.name}.response.408.time`, Number(duration));
61
+ this.logResponseMetrics(status, duration);
62
62
  }
63
63
  throw error;
64
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { InstrumentedRESTDataSource } from './instrumented';
2
2
  import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest';
3
3
  export declare class OrigamiImageDataSource extends InstrumentedRESTDataSource {
4
4
  baseURL: string;
5
- backendSystemCodes: string[];
5
+ backendSystemCode: string;
6
6
  abortController: AbortController;
7
7
  timeout: ReturnType<typeof setTimeout> | undefined;
8
8
  imageMetadataCacheTTL: number;
@@ -1,14 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OrigamiImageDataSource = void 0;
4
- const errors_1 = require("@dotcom-reliability-kit/errors");
5
4
  const instrumented_1 = require("./instrumented");
6
5
  const REQUEST_TIMEOUT = 5000; // 5 seconds
7
6
  class OrigamiImageDataSource extends instrumented_1.InstrumentedRESTDataSource {
8
7
  constructor() {
9
8
  super(...arguments);
10
9
  this.baseURL = 'https://www.ft.com/__origami/service/image/v2/';
11
- this.backendSystemCodes = ['origami-image-service-v2'];
10
+ this.backendSystemCode = 'origami-image-service-v2';
12
11
  this.abortController = new AbortController();
13
12
  this.timeout = undefined;
14
13
  this.imageMetadataCacheTTL = process.env.IMAGE_METADATA_CACHE_TTL
@@ -24,26 +23,13 @@ class OrigamiImageDataSource extends instrumented_1.InstrumentedRESTDataSource {
24
23
  return { ttl: this.imageMetadataCacheTTL };
25
24
  }
26
25
  async getImageMetadata(url) {
27
- try {
28
- const imageMetadata = await this.get(`images/metadata/${encodeURIComponent(url)}?source=next`);
29
- this.calls.push(url);
30
- if (this.timeout) {
31
- clearTimeout(this.timeout);
32
- this.timeout = undefined;
33
- }
34
- return imageMetadata;
35
- }
36
- catch (error) {
37
- if (error instanceof Error && error.name === 'AbortError') {
38
- throw new errors_1.UpstreamServiceError({
39
- code: 'ORIGAMI_DATASOURCE_TIMEOUT',
40
- message: `Request to Image Service took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
41
- statusCode: 408,
42
- relatesToSystems: this.backendSystemCodes,
43
- });
44
- }
45
- throw error;
26
+ const imageMetadata = await this.get(`images/metadata/${encodeURIComponent(url)}?source=next`);
27
+ this.calls.push(url);
28
+ if (this.timeout) {
29
+ clearTimeout(this.timeout);
30
+ this.timeout = undefined;
46
31
  }
32
+ return imageMetadata;
47
33
  }
48
34
  }
49
35
  exports.OrigamiImageDataSource = OrigamiImageDataSource;
@@ -1 +1 @@
1
- {"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":";;;AAAA,2DAAqE;AACrE,iDAA2D;AAG3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAa,sBAAuB,SAAQ,yCAA0B;IAAtE;;QACE,YAAO,GAAG,gDAAgD,CAAA;QAC1D,uBAAkB,GAAG,CAAC,0BAA0B,CAAC,CAAA;QAEjD,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;QAE9D,0BAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB;YAC1D,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YAChD,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA,CAAC,SAAS;IA4ChC,CAAC;IA1CU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAES,eAAe;QACvB,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,IAAI;YACF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,GAAG,CAClC,mBAAmB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CACzD,CAAA;YAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAEpB,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;aACzB;YACD,OAAO,aAAa,CAAA;SACrB;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,MAAM,IAAI,6BAAoB,CAAC;oBAC7B,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,6CAA6C,eAAe,8BAA8B;oBACnG,UAAU,EAAE,GAAG;oBACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;iBAC1C,CAAC,CAAA;aACH;YAED,MAAM,KAAK,CAAA;SACZ;IACH,CAAC;CACF;AArDD,wDAqDC"}
1
+ {"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAG3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AAEzC,MAAa,sBAAuB,SAAQ,yCAA0B;IAAtE;;QACE,YAAO,GAAG,gDAAgD,CAAA;QAC1D,sBAAiB,GAAG,0BAA0B,CAAA;QAE9C,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;QAE9D,0BAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB;YAC1D,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YAChD,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA,CAAC,SAAS;IA+BhC,CAAC;IA7BU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAES,eAAe;QACvB,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,GAAG,CAClC,mBAAmB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CACzD,CAAA;QAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEpB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QACD,OAAO,aAAa,CAAA;IACtB,CAAC;CACF;AAxCD,wDAwCC"}
@@ -2,7 +2,7 @@ import { AugmentedRequest, CacheOptions } from '@apollo/datasource-rest';
2
2
  import { InstrumentedRESTDataSource } from './instrumented';
3
3
  export declare class TwitterDataSource extends InstrumentedRESTDataSource {
4
4
  baseURL: string;
5
- backendSystemCodes: string[];
5
+ backendSystemCode: string;
6
6
  abortController: AbortController;
7
7
  timeout: ReturnType<typeof setTimeout> | undefined;
8
8
  willSendRequest(path: string, request: AugmentedRequest): void;
@@ -1,14 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TwitterDataSource = void 0;
4
- const errors_1 = require("@dotcom-reliability-kit/errors");
5
4
  const instrumented_1 = require("./instrumented");
6
5
  const REQUEST_TIMEOUT = 5000; // 5 seconds
7
6
  class TwitterDataSource extends instrumented_1.InstrumentedRESTDataSource {
8
7
  constructor() {
9
8
  super(...arguments);
10
9
  this.baseURL = 'https://publish.twitter.com';
11
- this.backendSystemCodes = ['twitter-oembed-api'];
10
+ this.backendSystemCode = 'twitter-oembed-api';
12
11
  this.abortController = new AbortController();
13
12
  this.timeout = undefined;
14
13
  }
@@ -18,26 +17,13 @@ class TwitterDataSource extends instrumented_1.InstrumentedRESTDataSource {
18
17
  this.timeout = setTimeout(() => this.abortController.abort(), REQUEST_TIMEOUT);
19
18
  }
20
19
  async getTweet(tweetUrl) {
21
- try {
22
- const tweet = await this.get(`oembed?url=${tweetUrl}&omit_script=true`);
23
- this.calls.push(tweetUrl);
24
- if (this.timeout) {
25
- clearTimeout(this.timeout);
26
- this.timeout = undefined;
27
- }
28
- return tweet;
29
- }
30
- catch (error) {
31
- if (error instanceof Error && error.name === 'AbortError') {
32
- throw new errors_1.UpstreamServiceError({
33
- code: 'TWITTER_DATASOURCE_TIMEOUT',
34
- message: `Request to Twitter API took longer than ${REQUEST_TIMEOUT}ms, and so has been aborted.`,
35
- statusCode: 408,
36
- relatesToSystems: this.backendSystemCodes,
37
- });
38
- }
39
- throw error;
20
+ const tweet = await this.get(`oembed?url=${tweetUrl}&omit_script=true`);
21
+ this.calls.push(tweetUrl);
22
+ if (this.timeout) {
23
+ clearTimeout(this.timeout);
24
+ this.timeout = undefined;
40
25
  }
26
+ return tweet;
41
27
  }
42
28
  cacheOptionsFor() {
43
29
  // HACK:20221110:KB twitter does return a cache_age property, but cacheOptionsFor
@@ -1 +1 @@
1
- {"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/datasources/twitter.ts"],"names":[],"mappings":";;;AACA,2DAAqE;AACrE,iDAA2D;AAE3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AACzC,MAAa,iBAAkB,SAAQ,yCAA0B;IAAjE;;QACE,YAAO,GAAG,6BAA6B,CAAA;QACvC,uBAAkB,GAAG,CAAC,oBAAoB,CAAC,CAAA;QAE3C,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IA2ChE,CAAC;IAzCU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7B,IAAI;YACF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,cAAc,QAAQ,mBAAmB,CAAC,CAAA;YAEvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAEzB,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;aACzB;YAED,OAAO,KAAK,CAAA;SACb;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,MAAM,IAAI,6BAAoB,CAAC;oBAC7B,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,2CAA2C,eAAe,8BAA8B;oBACjG,UAAU,EAAE,GAAG;oBACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;iBAC1C,CAAC,CAAA;aACH;YAED,MAAM,KAAK,CAAA;SACZ;IACH,CAAC;IAED,eAAe;QACb,iFAAiF;QACjF,oFAAoF;QACpF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACtB,CAAC;CACF;AAhDD,8CAgDC"}
1
+ {"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/datasources/twitter.ts"],"names":[],"mappings":";;;AACA,iDAA2D;AAE3D,MAAM,eAAe,GAAG,IAAI,CAAA,CAAC,YAAY;AACzC,MAAa,iBAAkB,SAAQ,yCAA0B;IAAjE;;QACE,YAAO,GAAG,6BAA6B,CAAA;QACvC,sBAAiB,GAAG,oBAAoB,CAAA;QAExC,oBAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QACvC,YAAO,GAA8C,SAAS,CAAA;IA8BhE,CAAC;IA5BU,eAAe,CAAC,IAAY,EAAE,OAAyB;QAC9D,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAA;QAC5C,IAAI,CAAC,OAAO,GAAG,UAAU,CACvB,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAClC,eAAe,CAChB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,cAAc,QAAQ,mBAAmB,CAAC,CAAA;QAEvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEzB,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;SACzB;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAED,eAAe;QACb,iFAAiF;QACjF,oFAAoF;QACpF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;IACtB,CAAC;CACF;AAnCD,8CAmCC"}
@@ -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';
@@ -972,6 +973,7 @@ export type LiveBlogPost = Content & {
972
973
  readonly altTitle?: Maybe<AltTitle>;
973
974
  /** An array of concepts related to the article, eg. organisations or topics. */
974
975
  readonly annotations?: Maybe<ReadonlyArray<Maybe<Concept>>>;
976
+ readonly authors?: Maybe<ReadonlyArray<Maybe<Person>>>;
975
977
  /** An abstract syntax tree of the article content. */
976
978
  readonly body?: Maybe<RichText>;
977
979
  /** The raw string of the XML as returned from ContentAPI. */
@@ -1077,6 +1079,16 @@ export type OpinionTopper = Topper & TopperWithHeadshot & TopperWithTheme & {
1077
1079
  readonly textShadow?: Maybe<Scalars['Boolean']['output']>;
1078
1080
  };
1079
1081
  export type OpinionTopperHeadshotArgs = {
1082
+ dpr?: InputMaybe<Scalars['Int']['input']>;
1083
+ url?: InputMaybe<Scalars['String']['input']>;
1084
+ width?: InputMaybe<Scalars['Int']['input']>;
1085
+ };
1086
+ export type Person = {
1087
+ readonly headshot?: Maybe<Scalars['String']['output']>;
1088
+ readonly prefLabel?: Maybe<Scalars['String']['output']>;
1089
+ readonly streamPage?: Maybe<Scalars['String']['output']>;
1090
+ };
1091
+ export type PersonHeadshotArgs = {
1080
1092
  dpr?: InputMaybe<Scalars['Int']['input']>;
1081
1093
  width?: InputMaybe<Scalars['Int']['input']>;
1082
1094
  };
@@ -1234,6 +1246,7 @@ export type PodcastTopper = Topper & TopperWithBrand & TopperWithHeadshot & Topp
1234
1246
  };
1235
1247
  export type PodcastTopperHeadshotArgs = {
1236
1248
  dpr?: InputMaybe<Scalars['Int']['input']>;
1249
+ url?: InputMaybe<Scalars['String']['input']>;
1237
1250
  width?: InputMaybe<Scalars['Int']['input']>;
1238
1251
  };
1239
1252
  export type Query = {
@@ -1390,6 +1403,7 @@ export type TopperWithHeadshot = {
1390
1403
  };
1391
1404
  export type TopperWithHeadshotHeadshotArgs = {
1392
1405
  dpr?: InputMaybe<Scalars['Int']['input']>;
1406
+ url?: InputMaybe<Scalars['String']['input']>;
1393
1407
  width?: InputMaybe<Scalars['Int']['input']>;
1394
1408
  };
1395
1409
  export type TopperWithImages = {
@@ -1582,6 +1596,7 @@ export type ResolversTypes = ResolversObject<{
1582
1596
  Mutation: ResolverTypeWrapper<{}>;
1583
1597
  OpinionTopper: ResolverTypeWrapper<TopperModel>;
1584
1598
  PackageDesign: ResolverTypeWrapper<Scalars['PackageDesign']['output']>;
1599
+ Person: ResolverTypeWrapper<PersonModel>;
1585
1600
  Picture: ResolverTypeWrapper<PictureModel>;
1586
1601
  PictureFullBleed: ResolverTypeWrapper<PictureModel>;
1587
1602
  PictureInline: ResolverTypeWrapper<PictureModel>;
@@ -1671,6 +1686,7 @@ export type ResolversParentTypes = ResolversObject<{
1671
1686
  Mutation: {};
1672
1687
  OpinionTopper: TopperModel;
1673
1688
  PackageDesign: Scalars['PackageDesign']['output'];
1689
+ Person: PersonModel;
1674
1690
  Picture: PictureModel;
1675
1691
  PictureFullBleed: PictureModel;
1676
1692
  PictureInline: PictureModel;
@@ -2187,6 +2203,7 @@ export type LiveBlogPostResolvers<ContextType = QueryContext, ParentType extends
2187
2203
  altStandfirst: Resolver<Maybe<ResolversTypes['AltStandfirst']>, ParentType, ContextType>;
2188
2204
  altTitle: Resolver<Maybe<ResolversTypes['AltTitle']>, ParentType, ContextType>;
2189
2205
  annotations: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Concept']>>>, ParentType, ContextType>;
2206
+ authors: Resolver<Maybe<ReadonlyArray<Maybe<ResolversTypes['Person']>>>, ParentType, ContextType>;
2190
2207
  body: Resolver<Maybe<ResolversTypes['RichText']>, ParentType, ContextType>;
2191
2208
  bodyXML: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2192
2209
  byline: Resolver<Maybe<ResolversTypes['StructuredContent']>, ParentType, ContextType, Partial<LiveBlogPostBylineArgs>>;
@@ -2248,6 +2265,12 @@ export type OpinionTopperResolvers<ContextType = QueryContext, ParentType extend
2248
2265
  export interface PackageDesignScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['PackageDesign'], any> {
2249
2266
  name: 'PackageDesign';
2250
2267
  }
2268
+ export type PersonResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person']> = ResolversObject<{
2269
+ headshot: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, Partial<PersonHeadshotArgs>>;
2270
+ prefLabel: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2271
+ streamPage: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2272
+ __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
2273
+ }>;
2251
2274
  export type PictureResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['Picture'] = ResolversParentTypes['Picture']> = ResolversObject<{
2252
2275
  __resolveType?: TypeResolveFn<'PictureFullBleed' | 'PictureInline' | 'PictureStandard', ParentType, ContextType>;
2253
2276
  alt: Resolver<ResolversTypes['String'], ParentType, ContextType>;
@@ -2537,6 +2560,7 @@ export type Resolvers<ContextType = QueryContext> = ResolversObject<{
2537
2560
  Mutation: MutationResolvers<ContextType>;
2538
2561
  OpinionTopper: OpinionTopperResolvers<ContextType>;
2539
2562
  PackageDesign: GraphQLScalarType;
2563
+ Person: PersonResolvers<ContextType>;
2540
2564
  Picture: PictureResolvers<ContextType>;
2541
2565
  PictureFullBleed: PictureFullBleedResolvers<ContextType>;
2542
2566
  PictureInline: PictureInlineResolvers<ContextType>;
@@ -2,6 +2,7 @@ import type { ImageSet, ContentTypeSchemas, ClipSet } from '../types/internal-co
2
2
  import type { QueryContext } from '..';
3
3
  import { CAPIImage } from './Image';
4
4
  import { Concept } from './Concept';
5
+ import { Person } from './Person';
5
6
  import { AccessLevel, ContentType, PackageDesign, CanBeSyndicated } from '../resolvers/scalars';
6
7
  import { LiteralUnionScalarValues } from '../resolvers/literal-union';
7
8
  import { ContentPackageContainsArgs, ContentUrlArgs, Media, TableOfContents } from '../generated';
@@ -73,6 +74,7 @@ export declare class CapiResponse {
73
74
  backgroundBox?: boolean | undefined;
74
75
  textShadow?: boolean | undefined;
75
76
  } | null | undefined;
77
+ authors(): Person[] | null;
76
78
  accessLevel(): LiteralUnionScalarValues<typeof AccessLevel>;
77
79
  editorialDesk(): string | null;
78
80
  canBeSyndicated(): LiteralUnionScalarValues<typeof CanBeSyndicated>;
@@ -17,6 +17,7 @@ const errors_1 = require("@dotcom-reliability-kit/errors");
17
17
  const lodash_sortby_1 = __importDefault(require("lodash.sortby"));
18
18
  const Image_1 = require("./Image");
19
19
  const Concept_1 = require("./Concept");
20
+ const Person_1 = require("./Person");
20
21
  const isError_1 = __importDefault(require("../helpers/isError"));
21
22
  const metadata_1 = require("../helpers/metadata");
22
23
  const capi_1 = require("./schemas/capi");
@@ -101,7 +102,7 @@ class CapiResponse {
101
102
  * Don't let this failure block this system from continuing to try and handle the request
102
103
  */
103
104
  if (!schemaResponse.success) {
104
- context.logger.error({
105
+ context.logger.warn({
105
106
  event: 'RECOVERABLE_ERROR',
106
107
  error: new errors_1.OperationalError({
107
108
  message: 'The data received from the CAPI data source does not match our data source schema. It is likely that our schema will require updating to handle all possible responses from CAPI.',
@@ -170,7 +171,7 @@ class CapiResponse {
170
171
  }
171
172
  catch (error) {
172
173
  if ((0, isError_1.default)(error)) {
173
- this.context.logger.error({
174
+ this.context.logger.warn({
174
175
  event: 'RECOVERABLE_ERROR',
175
176
  error,
176
177
  });
@@ -189,6 +190,12 @@ class CapiResponse {
189
190
  return this.capiData.topper;
190
191
  return null;
191
192
  }
193
+ authors() {
194
+ const authors = this.getAuthors();
195
+ return authors.length === 0
196
+ ? null
197
+ : authors.map((author) => new Person_1.Person(author, this, this.context));
198
+ }
192
199
  accessLevel() {
193
200
  return this.capiData.accessLevel ?? 'subscribed';
194
201
  }
@@ -277,7 +284,22 @@ class CapiResponse {
277
284
  else {
278
285
  const containerId = 'containedIn' in this.capiData && this.capiData.containedIn?.[0]?.id;
279
286
  if (containerId) {
280
- container = await this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(containerId));
287
+ try {
288
+ container = await this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(containerId));
289
+ }
290
+ catch (error) {
291
+ if (error instanceof Error) {
292
+ this.context.logger.warn({
293
+ event: 'RECOVERABLE_ERROR',
294
+ error: new errors_1.OperationalError({
295
+ message: `Failed to fetch package container ${containerId} for content ${this.id()}`,
296
+ code: `PACKAGE_CONTAINEDIN_ERROR`,
297
+ cause: error,
298
+ }),
299
+ });
300
+ }
301
+ return null;
302
+ }
281
303
  this.packageContainer = container;
282
304
  }
283
305
  }
@@ -419,7 +441,21 @@ class CapiResponse {
419
441
  ? this.capiData.contains.slice(Math.max(0, fromIndex - surroundingArticles), fromIndex + surroundingArticles)
420
442
  : this.capiData.contains;
421
443
  this.context.addSurrogateKeys(contains.map((article) => article.id));
422
- return Promise.all(contains.map(({ id }) => this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(id), this)));
444
+ const results = await Promise.allSettled(contains.map(({ id }) => this.context.dataSources.capi.getContent((0, metadata_1.uuidFromUrl)(id), this)));
445
+ const failed = results.filter((result) => result.status === 'rejected');
446
+ if (failed.length) {
447
+ this.context.logger.warn({
448
+ event: 'RECOVERABLE_ERROR',
449
+ error: new errors_1.OperationalError({
450
+ code: 'PACKAGE_CONTAINS_ERROR',
451
+ message: `Failed to fetch some of the content contained in the package ${this.id()}`,
452
+ cause: new AggregateError(failed.map((result) => result.reason)),
453
+ }),
454
+ });
455
+ }
456
+ return results
457
+ .filter((result) => result.status === 'fulfilled')
458
+ .map((result) => result.value);
423
459
  }
424
460
  return null;
425
461
  }