@adobe/spacecat-shared-data-access 3.75.2 → 3.75.4

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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## [@adobe/spacecat-shared-data-access-v3.75.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.75.3...@adobe/spacecat-shared-data-access-v3.75.4) (2026-06-16)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **data-access:** batch suggestion-grant lookups to avoid 414 URI Too Long ([#1684](https://github.com/adobe/spacecat-shared/issues/1684)) ([941660c](https://github.com/adobe/spacecat-shared/commit/941660cea9ea37cc2e4bf50528923f9f051b540e))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.75.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.75.2...@adobe/spacecat-shared-data-access-v3.75.3) (2026-06-16)
8
+
9
+ ### Bug Fixes
10
+
11
+ * add EBUSY retry support to S3Client via custom retry strategy ([#1677](https://github.com/adobe/spacecat-shared/issues/1677)) ([04e5504](https://github.com/adobe/spacecat-shared/commit/04e550481cdff0e7545036320aaa43ed64e99ce3)), closes [#1659](https://github.com/adobe/spacecat-shared/issues/1659) [#1659](https://github.com/adobe/spacecat-shared/issues/1659)
12
+
1
13
  ## [@adobe/spacecat-shared-data-access-v3.75.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.75.1...@adobe/spacecat-shared-data-access-v3.75.2) (2026-06-16)
2
14
 
3
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.75.2",
3
+ "version": "3.75.4",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -44,6 +44,7 @@
44
44
  "@adobe/spacecat-shared-utils": "1.105.0",
45
45
  "@supabase/postgrest-js": "2.106.2",
46
46
  "@aws-sdk/client-s3": "^3.940.0",
47
+ "@smithy/util-retry": "^4.0.0",
47
48
  "@types/joi": "17.2.3",
48
49
  "aws-xray-sdk": "3.12.0",
49
50
  "joi": "18.2.1",
@@ -27,6 +27,10 @@ const S3_CONFIG_KEY = 'config/spacecat/global-config.json';
27
27
  * Unlike other collections, this uses S3 instead of PostgREST.
28
28
  * Configuration is stored as a versioned JSON object in S3.
29
29
  *
30
+ * The S3Client is configured with a custom retry strategy (EbusyRetryStrategy) that extends
31
+ * the AWS SDK's StandardRetryStrategy to also retry EBUSY DNS errors. This handles the chronic
32
+ * DNS resolver exhaustion issue in Lambda without adding application-level retry logic.
33
+ *
30
34
  * @class ConfigurationCollection
31
35
  */
32
36
  class ConfigurationCollection {
@@ -27,8 +27,18 @@ import DataAccessError from '../../errors/data-access.error.js';
27
27
  class SuggestionGrantCollection extends BaseCollection {
28
28
  static COLLECTION_NAME = 'SuggestionGrantCollection';
29
29
 
30
+ /**
31
+ * Max suggestion IDs per PostgREST request. PostgREST serializes `.in()` into the request
32
+ * URL (`suggestion_id=in.(<uuid>,<uuid>,...)`); each UUID costs ~39 chars, so 100 IDs keeps
33
+ * the query string near ~4KB — comfortably under the ~8KB limit that otherwise triggers a
34
+ * `414 URI Too Long` and fails the whole lookup.
35
+ */
36
+ static FIND_BY_SUGGESTION_IDS_CHUNK_SIZE = 100;
37
+
30
38
  /**
31
39
  * Finds all grant rows for the given suggestion IDs (suggestion_id, grant_id only).
40
+ * Lookups are chunked to avoid `414 URI Too Long` from PostgREST GET URLs when many
41
+ * suggestion IDs are supplied.
32
42
  *
33
43
  * @async
34
44
  * @param {string[]} suggestionIds - Suggestion IDs to look up.
@@ -39,16 +49,28 @@ class SuggestionGrantCollection extends BaseCollection {
39
49
  if (!Array.isArray(suggestionIds) || suggestionIds.length === 0) {
40
50
  return [];
41
51
  }
42
- const { data, error } = await this.postgrestService
43
- .from(this.tableName)
44
- .select('suggestion_id,grant_id')
45
- .in('suggestion_id', suggestionIds);
46
52
 
47
- if (error) {
48
- throw new DataAccessError('Failed to find grants by suggestion IDs', this, error);
53
+ const chunkSize = SuggestionGrantCollection.FIND_BY_SUGGESTION_IDS_CHUNK_SIZE;
54
+ const rows = [];
55
+
56
+ for (let i = 0; i < suggestionIds.length; i += chunkSize) {
57
+ const chunk = suggestionIds.slice(i, i + chunkSize);
58
+ // eslint-disable-next-line no-await-in-loop
59
+ const { data, error } = await this.postgrestService
60
+ .from(this.tableName)
61
+ .select('suggestion_id,grant_id')
62
+ .in('suggestion_id', chunk);
63
+
64
+ if (error) {
65
+ throw new DataAccessError('Failed to find grants by suggestion IDs', this, error);
66
+ }
67
+
68
+ if (data) {
69
+ rows.push(...data);
70
+ }
49
71
  }
50
72
 
51
- return data ?? [];
73
+ return rows;
52
74
  }
53
75
 
54
76
  /**
@@ -13,6 +13,7 @@
13
13
  import { S3Client } from '@aws-sdk/client-s3';
14
14
  import { PostgrestClient } from '@supabase/postgrest-js';
15
15
  import { h1NoCache, keepAliveNoCache } from '@adobe/fetch';
16
+ import { StandardRetryStrategy } from '@smithy/util-retry';
16
17
 
17
18
  import { instrumentAWSClient } from '@adobe/spacecat-shared-utils';
18
19
  import { EntityRegistry } from '../models/index.js';
@@ -78,6 +79,37 @@ const createPostgrestService = (config, client = undefined) => {
78
79
  });
79
80
  };
80
81
 
82
+ /**
83
+ * Custom retry strategy that extends StandardRetryStrategy to include EBUSY errors.
84
+ * The AWS SDK's default retry strategy handles ECONNRESET, ETIMEDOUT, ENOTFOUND, etc.,
85
+ * but does NOT retry EBUSY DNS errors. This class adds EBUSY to the retryable set.
86
+ *
87
+ * StandardRetryStrategy implements RetryStrategyV2 interface, which uses
88
+ * refreshRetryTokenForRetry() rather than retry(). The SDK's retry middleware
89
+ * calls this method to determine if an error should be retried.
90
+ */
91
+ export class EbusyRetryStrategy extends StandardRetryStrategy {
92
+ constructor(maxAttempts = 4) {
93
+ super(maxAttempts);
94
+ }
95
+
96
+ async refreshRetryTokenForRetry(token, errorInfo) {
97
+ const { error } = errorInfo;
98
+
99
+ // Check if this is an EBUSY error (code or message)
100
+ const isEbusy = error?.code === 'EBUSY'
101
+ || (error?.message?.includes('getaddrinfo') && error?.message?.includes('EBUSY'));
102
+
103
+ if (isEbusy) {
104
+ // Reclassify EBUSY as TRANSIENT so StandardRetryStrategy will retry it
105
+ return super.refreshRetryTokenForRetry(token, { ...errorInfo, errorType: 'TRANSIENT' });
106
+ }
107
+
108
+ // Delegate to StandardRetryStrategy for all other errors
109
+ return super.refreshRetryTokenForRetry(token, errorInfo);
110
+ }
111
+ }
112
+
81
113
  /**
82
114
  * Creates an S3 service configuration if bucket configuration is provided.
83
115
  *
@@ -93,7 +125,11 @@ const createS3Service = (config) => {
93
125
  return null;
94
126
  }
95
127
 
96
- const options = region ? { region } : {};
128
+ const options = {
129
+ ...(region ? { region } : {}),
130
+ maxAttempts: 4,
131
+ retryStrategy: new EbusyRetryStrategy(4),
132
+ };
97
133
  const s3Client = instrumentAWSClient(new S3Client(options));
98
134
 
99
135
  return { s3Client, s3Bucket };