@friggframework/core 2.0.0--canary.657.b14221f.0 → 2.0.0--canary.658.93c8e07.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.
@@ -14,6 +14,7 @@ const {
14
14
  IntegrationRepositoryInterface,
15
15
  } = require('./integration-repository-interface');
16
16
  const { validateConfigPatch } = require('./config-patch-shared');
17
+ const { toMessageItem } = require('./message-item-shared');
17
18
 
18
19
  class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
19
20
  constructor() {
@@ -94,7 +95,7 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
94
95
  async updateIntegrationMessages(
95
96
  integrationId,
96
97
  messageType,
97
- messageTitle,
98
+ messageTitleOrItem,
98
99
  messageBody,
99
100
  messageTimestamp
100
101
  ) {
@@ -108,11 +109,12 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
108
109
  }
109
110
  const messages = this._extractMessages(existing);
110
111
  const list = Array.isArray(messages[messageType]) ? [...messages[messageType]] : [];
111
- list.push({
112
- title: messageTitle ?? null,
113
- message: messageBody,
114
- timestamp: messageTimestamp,
115
- });
112
+ const item = toMessageItem(
113
+ messageTitleOrItem,
114
+ messageBody,
115
+ messageTimestamp
116
+ );
117
+ list.push({ ...item, title: item.title ?? null });
116
118
  const updatedMessages = { ...messages, [messageType]: list };
117
119
  await updateOne(
118
120
  this.prisma,
@@ -132,6 +134,23 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
132
134
  return true;
133
135
  }
134
136
 
137
+ async findIntegrationMessages(integrationId, messageType) {
138
+ const objectId = toObjectId(integrationId);
139
+ if (!objectId) {
140
+ throw new Error(`Integration ${integrationId} not found`);
141
+ }
142
+ const existing = await findOne(this.prisma, 'Integration', {
143
+ _id: objectId,
144
+ });
145
+ if (!existing) {
146
+ throw new Error(`Integration ${integrationId} not found`);
147
+ }
148
+ const messages = this._extractMessages(existing);
149
+ return Array.isArray(messages[messageType])
150
+ ? messages[messageType]
151
+ : [];
152
+ }
153
+
135
154
  async createIntegration(entities, userId, config) {
136
155
  const now = new Date();
137
156
  const document = {
@@ -97,26 +97,43 @@ class IntegrationRepositoryInterface {
97
97
  }
98
98
 
99
99
  /**
100
- * Update integration messages
100
+ * Append a message to the stored messages of one type. Takes the
101
+ * positional form (title, body, timestamp) or one message item object,
102
+ * whose keys are stored as they are.
101
103
  *
102
104
  * @param {string|number} integrationId - Integration ID
103
105
  * @param {string} messageType - Type of message (errors, warnings, info, logs)
104
- * @param {string} messageTitle - Message title
105
- * @param {string} messageBody - Message body
106
- * @param {Date} messageTimestamp - Message timestamp
106
+ * @param {string|Object} messageTitleOrItem - Message title, or the whole message item
107
+ * @param {string} [messageBody] - Message body (positional form)
108
+ * @param {Date} [messageTimestamp] - Message timestamp (positional form)
107
109
  * @returns {Promise<boolean>} Success indicator
108
110
  * @abstract
109
111
  */
110
112
  async updateIntegrationMessages(
111
113
  integrationId,
112
114
  messageType,
113
- messageTitle,
115
+ messageTitleOrItem,
114
116
  messageBody,
115
117
  messageTimestamp
116
118
  ) {
117
119
  throw new Error('Method updateIntegrationMessages must be implemented by subclass');
118
120
  }
119
121
 
122
+ /**
123
+ * Find the stored messages of one type. Reads the stored items, not the
124
+ * `messages` of an integration record, which some adapters do not fill.
125
+ *
126
+ * @param {string|number} integrationId - Integration ID
127
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
128
+ * @returns {Promise<Array>} The stored message items of that type, oldest first
129
+ * @abstract
130
+ */
131
+ async findIntegrationMessages(integrationId, messageType) {
132
+ throw new Error(
133
+ 'Method findIntegrationMessages must be implemented by subclass'
134
+ );
135
+ }
136
+
120
137
  /**
121
138
  * Create a new integration
122
139
  *
@@ -3,6 +3,7 @@ const {
3
3
  IntegrationRepositoryInterface,
4
4
  } = require('./integration-repository-interface');
5
5
  const { validateConfigPatch } = require('./config-patch-shared');
6
+ const { toMessageItem } = require('./message-item-shared');
6
7
 
7
8
  /**
8
9
  * MongoDB Integration Repository Adapter
@@ -229,15 +230,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
229
230
  *
230
231
  * @param {string} integrationId - Integration ID
231
232
  * @param {string} messageType - Type of message (errors, warnings, info, logs)
232
- * @param {string} messageTitle - Message title
233
- * @param {string} messageBody - Message body
234
- * @param {Date} messageTimestamp - Message timestamp
233
+ * @param {string|Object} messageTitleOrItem - Message title, or the whole message item
234
+ * @param {string} [messageBody] - Message body (positional form)
235
+ * @param {Date} [messageTimestamp] - Message timestamp (positional form)
235
236
  * @returns {Promise<boolean>} Success indicator
236
237
  */
237
238
  async updateIntegrationMessages(
238
239
  integrationId,
239
240
  messageType,
240
- messageTitle,
241
+ messageTitleOrItem,
241
242
  messageBody,
242
243
  messageTimestamp
243
244
  ) {
@@ -251,17 +252,14 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
251
252
  }
252
253
 
253
254
  // Parse existing messages (JSON field)
254
- const messages = integration.messages || {};
255
- const messageArray = Array.isArray(messages[messageType])
256
- ? messages[messageType]
255
+ const messageArray = Array.isArray(integration[messageType])
256
+ ? integration[messageType]
257
257
  : [];
258
258
 
259
259
  // Add new message
260
- messageArray.push({
261
- title: messageTitle,
262
- message: messageBody,
263
- timestamp: messageTimestamp,
264
- });
260
+ messageArray.push(
261
+ toMessageItem(messageTitleOrItem, messageBody, messageTimestamp)
262
+ );
265
263
 
266
264
  // Update messages
267
265
  await this.prisma.integration.update({
@@ -274,6 +272,28 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
274
272
  return true; // Mongoose compatibility
275
273
  }
276
274
 
275
+ /**
276
+ * Find the stored messages of one type
277
+ *
278
+ * @param {string} integrationId - Integration ID
279
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
280
+ * @returns {Promise<Array>} The stored message items of that type, oldest first
281
+ */
282
+ async findIntegrationMessages(integrationId, messageType) {
283
+ const integration = await this.prisma.integration.findUnique({
284
+ where: { id: integrationId },
285
+ select: { [messageType]: true },
286
+ });
287
+
288
+ if (!integration) {
289
+ throw new Error(`Integration ${integrationId} not found`);
290
+ }
291
+
292
+ return Array.isArray(integration[messageType])
293
+ ? integration[messageType]
294
+ : [];
295
+ }
296
+
277
297
  /**
278
298
  * Find all integrations whose entity set includes the given entity ID.
279
299
  *
@@ -3,6 +3,7 @@ const {
3
3
  IntegrationRepositoryInterface,
4
4
  } = require('./integration-repository-interface');
5
5
  const { validateConfigPatch } = require('./config-patch-shared');
6
+ const { toMessageItem } = require('./message-item-shared');
6
7
  const { strictIntId } = require('./report-id');
7
8
 
8
9
  /**
@@ -273,15 +274,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
273
274
  *
274
275
  * @param {string} integrationId - Integration ID (string from application layer)
275
276
  * @param {string} messageType - Type of message (errors, warnings, info, logs)
276
- * @param {string} messageTitle - Message title
277
- * @param {string} messageBody - Message body
278
- * @param {Date} messageTimestamp - Message timestamp
277
+ * @param {string|Object} messageTitleOrItem - Message title, or the whole message item
278
+ * @param {string} [messageBody] - Message body (positional form)
279
+ * @param {Date} [messageTimestamp] - Message timestamp (positional form)
279
280
  * @returns {Promise<boolean>} Success indicator
280
281
  */
281
282
  async updateIntegrationMessages(
282
283
  integrationId,
283
284
  messageType,
284
- messageTitle,
285
+ messageTitleOrItem,
285
286
  messageBody,
286
287
  messageTimestamp
287
288
  ) {
@@ -297,17 +298,14 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
297
298
  }
298
299
 
299
300
  // Parse existing messages (JSON field)
300
- const messages = integration.messages || {};
301
- const messageArray = Array.isArray(messages[messageType])
302
- ? messages[messageType]
301
+ const messageArray = Array.isArray(integration[messageType])
302
+ ? integration[messageType]
303
303
  : [];
304
304
 
305
305
  // Add new message
306
- messageArray.push({
307
- title: messageTitle,
308
- message: messageBody,
309
- timestamp: messageTimestamp,
310
- });
306
+ messageArray.push(
307
+ toMessageItem(messageTitleOrItem, messageBody, messageTimestamp)
308
+ );
311
309
 
312
310
  // Update messages
313
311
  await this.prisma.integration.update({
@@ -320,6 +318,29 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
320
318
  return true; // Mongoose compatibility
321
319
  }
322
320
 
321
+ /**
322
+ * Find the stored messages of one type
323
+ *
324
+ * @param {string} integrationId - Integration ID (string from application layer)
325
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
326
+ * @returns {Promise<Array>} The stored message items of that type, oldest first
327
+ */
328
+ async findIntegrationMessages(integrationId, messageType) {
329
+ const intId = this._convertId(integrationId);
330
+ const integration = await this.prisma.integration.findUnique({
331
+ where: { id: intId },
332
+ select: { [messageType]: true },
333
+ });
334
+
335
+ if (!integration) {
336
+ throw new Error(`Integration ${integrationId} not found`);
337
+ }
338
+
339
+ return Array.isArray(integration[messageType])
340
+ ? integration[messageType]
341
+ : [];
342
+ }
343
+
323
344
  /**
324
345
  * Create a new integration
325
346
  *
@@ -0,0 +1,24 @@
1
+ const isItem = (value) =>
2
+ value !== null && typeof value === 'object' && !Array.isArray(value);
3
+
4
+ /**
5
+ * The stored shape of a message, for IntegrationRepository
6
+ * .updateIntegrationMessages(). The method takes the positional form (title,
7
+ * body, timestamp) or one item object. The keys of an item object are stored
8
+ * as they are, so a caller can attach what a client acts on, like a code or
9
+ * a list of actions.
10
+ * @param {string|Object} titleOrItem - The title, or the whole item.
11
+ * @param {string} [messageBody] - Ignored when an item is given.
12
+ * @param {number|Date} [messageTimestamp] - Ignored when an item is given.
13
+ * @returns {Object} A new object; the given item is never returned itself.
14
+ */
15
+ function toMessageItem(titleOrItem, messageBody, messageTimestamp) {
16
+ if (isItem(titleOrItem)) return { ...titleOrItem };
17
+ return {
18
+ title: titleOrItem,
19
+ message: messageBody,
20
+ timestamp: messageTimestamp,
21
+ };
22
+ }
23
+
24
+ module.exports = { toMessageItem };
@@ -1,5 +1,6 @@
1
1
  const { v4: uuid } = require('uuid');
2
2
  const { validateConfigPatch } = require('../../repositories/config-patch-shared');
3
+ const { toMessageItem } = require('../../repositories/message-item-shared');
3
4
 
4
5
  class TestIntegrationRepository {
5
6
  constructor() {
@@ -72,18 +73,30 @@ class TestIntegrationRepository {
72
73
  return results;
73
74
  }
74
75
 
75
- async updateIntegrationMessages(id, type, title, body, timestamp) {
76
+ async updateIntegrationMessages(id, type, titleOrItem, body, timestamp) {
76
77
  const rec = this.store.get(id);
77
78
  if (!rec) {
78
79
  this.operationHistory.push({ operation: 'updateMessages', id, success: false });
79
80
  return false;
80
81
  }
81
82
  if (!rec.messages[type]) rec.messages[type] = [];
82
- rec.messages[type].push({ title, message: body, timestamp });
83
+ rec.messages[type].push(toMessageItem(titleOrItem, body, timestamp));
83
84
  this.operationHistory.push({ operation: 'updateMessages', id, type, success: true });
84
85
  return true;
85
86
  }
86
87
 
88
+ async findIntegrationMessages(id, type) {
89
+ const rec = this.store.get(id);
90
+ this.operationHistory.push({
91
+ operation: 'findMessages',
92
+ id,
93
+ type,
94
+ found: !!rec,
95
+ });
96
+ if (!rec) throw new Error(`Integration ${id} not found`);
97
+ return [...(rec.messages[type] ?? [])];
98
+ }
99
+
87
100
  async updateIntegrationConfig(id, config) {
88
101
  if (config === null || config === undefined) {
89
102
  throw new Error('Config parameter is required');
@@ -0,0 +1,85 @@
1
+ const RATE_LIMITED = 'RATE_LIMITED';
2
+ const SAME_LIMIT_WINDOW_MS = 60_000;
3
+ const MINUTE_MS = 60_000;
4
+
5
+ const isLink = (link) =>
6
+ typeof link?.label === 'string' && typeof link?.url === 'string';
7
+
8
+ const linkActions = (links) =>
9
+ (Array.isArray(links) ? links : [])
10
+ .filter(isLink)
11
+ .map(({ label, url }) => ({ type: 'LINK', label, url }));
12
+
13
+ function utcMinuteAfter(date) {
14
+ const minute = new Date(Math.ceil(date.getTime() / MINUTE_MS) * MINUTE_MS);
15
+ return `${minute.toISOString().slice(0, 16).replace('T', ' ')} UTC`;
16
+ }
17
+
18
+ /**
19
+ * Use case that records one user-facing warning when a module reports a rate
20
+ * limit that is too long to wait for. The text is fixed and the payload's
21
+ * message, url, body and headers are never read, because the message reaches
22
+ * end users. It changes no integration status: a rate limit is not an error.
23
+ * @class RecordRateLimitMessage
24
+ */
25
+ class RecordRateLimitMessage {
26
+ /**
27
+ * @param {Object} params - Configuration parameters.
28
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository - Repository for integration data operations.
29
+ */
30
+ constructor({ integrationRepository }) {
31
+ this.integrationRepository = integrationRepository;
32
+ }
33
+
34
+ /**
35
+ * Writes the warning, unless the stored warnings already hold one for the
36
+ * same module whose reset time is within a minute of this one. Many workers
37
+ * hit one limit together and each reports it, so the check reads the
38
+ * stored warnings and not the messages of this instance.
39
+ * @async
40
+ * @param {string} integrationId - ID of the integration to warn on.
41
+ * @param {Object} payload - The RATE_LIMITED payload of the Module.
42
+ * @param {string} payload.moduleName
43
+ * @param {string} payload.reason
44
+ * @param {Date|string|number} payload.retryAt
45
+ * @param {Array<{label: string, url: string}>} [payload.links]
46
+ * @returns {Promise<boolean>} True when it wrote a warning.
47
+ * @throws When the read or the write fails; the caller decides what a
48
+ * failed warning is worth.
49
+ */
50
+ async execute(integrationId, { moduleName, reason, retryAt, links }) {
51
+ const resetAt = new Date(retryAt);
52
+ const warnings =
53
+ await this.integrationRepository.findIntegrationMessages(
54
+ integrationId,
55
+ 'warnings'
56
+ );
57
+ const alreadyRecorded = warnings.some(
58
+ (warning) =>
59
+ warning?.code === RATE_LIMITED &&
60
+ warning.module === moduleName &&
61
+ Math.abs(Date.parse(warning.retryAt) - resetAt.getTime()) <=
62
+ SAME_LIMIT_WINDOW_MS
63
+ );
64
+ if (alreadyRecorded) return false;
65
+
66
+ const resetsAt = utcMinuteAfter(resetAt);
67
+ await this.integrationRepository.updateIntegrationMessages(
68
+ integrationId,
69
+ 'warnings',
70
+ {
71
+ title: 'Rate limit reached',
72
+ message: `The ${moduleName} API rate limit was reached and resets at ${resetsAt}.`,
73
+ timestamp: Date.now(),
74
+ code: RATE_LIMITED,
75
+ module: moduleName,
76
+ reason,
77
+ retryAt: resetAt.toISOString(),
78
+ actions: [{ type: 'RETRY_WHEN_READY' }, ...linkActions(links)],
79
+ }
80
+ );
81
+ return true;
82
+ }
83
+ }
84
+
85
+ module.exports = { RecordRateLimitMessage };
@@ -13,19 +13,21 @@ class UpdateIntegrationMessages {
13
13
  }
14
14
 
15
15
  /**
16
- * Executes the integration messages update.
16
+ * Executes the integration messages update. Pass the positional form
17
+ * (title, body, timestamp) or one message item object; the keys of an item
18
+ * object are stored as they are.
17
19
  * @async
18
20
  * @param {string} integrationId - ID of the integration to update.
19
21
  * @param {string} messageType - Type of message: 'errors', 'warnings', 'info', or 'logs'.
20
- * @param {string} messageTitle - Title of the message.
21
- * @param {string} messageBody - Body content of the message.
22
- * @param {string} messageTimestamp - Timestamp when the message was created.
22
+ * @param {string|Object} messageTitleOrItem - Title of the message, or the whole message item.
23
+ * @param {string} [messageBody] - Body content of the message (positional form).
24
+ * @param {string} [messageTimestamp] - Timestamp when the message was created (positional form).
23
25
  * @returns {Promise<Object>} The updated integration record.
24
26
  */
25
27
  async execute(
26
28
  integrationId,
27
29
  messageType,
28
- messageTitle,
30
+ messageTitleOrItem,
29
31
  messageBody,
30
32
  messageTimestamp
31
33
  ) {
@@ -33,7 +35,7 @@ class UpdateIntegrationMessages {
33
35
  await this.integrationRepository.updateIntegrationMessages(
34
36
  integrationId,
35
37
  messageType,
36
- messageTitle,
38
+ messageTitleOrItem,
37
39
  messageBody,
38
40
  messageTimestamp
39
41
  );
package/modules/module.js CHANGED
@@ -51,6 +51,8 @@ class Module extends Delegate {
51
51
  this.delegateTypes.push(this.DLGT_CREDENTIAL_INVALIDATED);
52
52
  this.DLGT_CREDENTIAL_VALIDATED = 'CREDENTIAL_VALIDATED';
53
53
  this.delegateTypes.push(this.DLGT_CREDENTIAL_VALIDATED);
54
+ this.DLGT_RATE_LIMITED = 'RATE_LIMITED';
55
+ this.delegateTypes.push(this.DLGT_RATE_LIMITED);
54
56
 
55
57
  Object.assign(this, this.definition.requiredAuthMethods);
56
58
 
@@ -160,6 +162,8 @@ class Module extends Delegate {
160
162
  await this.deauthorize();
161
163
  } else if (delegateString === this.api.DLGT_INVALID_AUTH) {
162
164
  await this.markCredentialsInvalid(object);
165
+ } else if (delegateString === this.api.DLGT_RATE_LIMITED) {
166
+ await this.reportRateLimit(object);
163
167
  } else if (delegateString === this.api.DLGT_CREDENTIAL_RELOAD) {
164
168
  return this.reloadCredential();
165
169
  }
@@ -234,6 +238,32 @@ class Module extends Delegate {
234
238
  }
235
239
  }
236
240
 
241
+ /**
242
+ * Passes a rate limit the api reported to the parent delegate, best
243
+ * effort. The payload never holds the error's message, url, body or
244
+ * headers, because the parent shows it to end users.
245
+ * @param {import('../errors').RateLimitError} rateLimitError
246
+ */
247
+ async reportRateLimit(rateLimitError) {
248
+ const { reason, retryAt, policy, statusCode } = rateLimitError;
249
+ const links = this.apiClass.rateLimit?.userHints?.[reason]?.links ?? [];
250
+ try {
251
+ await this.notify(this.DLGT_RATE_LIMITED, {
252
+ moduleName: this.name,
253
+ reason,
254
+ retryAt,
255
+ policy,
256
+ statusCode,
257
+ links,
258
+ });
259
+ } catch (err) {
260
+ this.logger.error('Failed to propagate RATE_LIMITED', {
261
+ eventName: `${this.logger.name}.rate_limited_propagation_failed`,
262
+ error: err,
263
+ });
264
+ }
265
+ }
266
+
237
267
  async deauthorize() {
238
268
  //todo: Check if this is correct, we're instantiating a new api without params (credentials, tokens, etc...)
239
269
  this.api = new this.apiClass();
@@ -398,12 +398,6 @@ class OAuth2Requester extends Requester {
398
398
  );
399
399
  transportError.statusCode = status;
400
400
  transportError.isTokenRefreshTransportFailure = true;
401
- if (error?.isRateLimited) {
402
- transportError.isRateLimited = true;
403
- transportError.retryAt = error.retryAt;
404
- transportError.waitMs = error.waitMs;
405
- transportError.reason = error.reason;
406
- }
407
401
  return transportError;
408
402
  }
409
403
 
@@ -67,6 +67,8 @@ class Requester extends Delegate {
67
67
  this._authGeneration = 0;
68
68
  this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
69
69
  this.delegateTypes.push(this.DLGT_INVALID_AUTH);
70
+ this.DLGT_RATE_LIMITED = 'RATE_LIMITED';
71
+ this.delegateTypes.push(this.DLGT_RATE_LIMITED);
70
72
  this.agent = get(params, 'agent', null);
71
73
 
72
74
  // Per-attempt HTTP timeout. Without this the framework called fetch()
@@ -557,6 +559,7 @@ class Requester extends Delegate {
557
559
  module: this._telemetryModuleLabel(),
558
560
  scopeKey: computeScopeKey(this._rateLimitPolicy, this),
559
561
  });
562
+ await this._notifyRateLimited(rateLimitError);
560
563
  throw this._maybeFlagTimeoutDuringBodyRead(rateLimitError, timeoutMs);
561
564
  }
562
565
 
@@ -636,6 +639,24 @@ class Requester extends Delegate {
636
639
  });
637
640
  }
638
641
 
642
+ /**
643
+ * Tells the delegate a wait was too long to sleep. Best effort: a failed
644
+ * notification is logged and never replaces the RateLimitError the caller
645
+ * is about to receive.
646
+ */
647
+ async _notifyRateLimited(rateLimitError) {
648
+ try {
649
+ await this.notify(this.DLGT_RATE_LIMITED, rateLimitError);
650
+ } catch (error) {
651
+ const logger = this.logger;
652
+ logger.warn('Rate limit notification failed', {
653
+ eventName: `${logger.name}.rate_limit_notify_failed`,
654
+ statusCode: rateLimitError.statusCode,
655
+ error,
656
+ });
657
+ }
658
+ }
659
+
639
660
  _logRequestFailed(encodedUrl, options, status) {
640
661
  const logger = this.logger;
641
662
  if (!logger.isLevelEnabled('DEBUG')) return;
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.657.b14221f.0",
4
+ "version": "2.0.0--canary.658.93c8e07.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
8
8
  "@aws-sdk/client-lambda": "^3.714.0",
9
9
  "@aws-sdk/client-s3": "^3.588.0",
10
- "@aws-sdk/client-scheduler": "^3.588.0",
11
10
  "@aws-sdk/client-sqs": "^3.588.0",
12
11
  "@aws-sdk/client-ssm": "^3.588.0",
13
12
  "@aws-sdk/s3-request-presigner": "^3.588.0",
@@ -48,9 +47,9 @@
48
47
  }
49
48
  },
50
49
  "devDependencies": {
51
- "@friggframework/eslint-config": "2.0.0--canary.657.b14221f.0",
52
- "@friggframework/prettier-config": "2.0.0--canary.657.b14221f.0",
53
- "@friggframework/test": "2.0.0--canary.657.b14221f.0",
50
+ "@friggframework/eslint-config": "2.0.0--canary.658.93c8e07.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.658.93c8e07.0",
52
+ "@friggframework/test": "2.0.0--canary.658.93c8e07.0",
54
53
  "@prisma/client": "^6.19.3",
55
54
  "@types/lodash": "4.17.15",
56
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -90,5 +89,5 @@
90
89
  "publishConfig": {
91
90
  "access": "public"
92
91
  },
93
- "gitHead": "b14221fca51737284a61407b42e003637930465e"
92
+ "gitHead": "93c8e078d4ff1bf00ed831152712b442abe615cd"
94
93
  }
@@ -126,4 +126,4 @@ const QueuerUtil = {
126
126
  },
127
127
  };
128
128
 
129
- module.exports = { QueuerUtil, awsConfigOptions };
129
+ module.exports = { QueuerUtil };
@@ -47,28 +47,8 @@ declare module "@friggframework/core" {
47
47
  send(params: object & { QueueUrl: any }, delay?: number): Promise<string>;
48
48
 
49
49
  sendAsyncSQSMessage(params: SendSQSMessageParams): Promise<string>;
50
-
51
- /**
52
- * Hook: a message was put back for a rate limit. Runs after the message
53
- * was sent. A failure is logged and never changes the outcome.
54
- */
55
- recordRateLimitWait(
56
- body: object,
57
- error: Error,
58
- state: RateLimitWaitState
59
- ): Promise<void>;
60
-
61
- /** Hook: a message that was put back for a rate limit ran without error. */
62
- clearRateLimitWait(body: object): Promise<void>;
63
50
  }
64
51
 
65
- export type RateLimitWaitState = {
66
- status: "WAITING" | "EXHAUSTED";
67
- mechanism: "delay" | "schedule" | "visibility" | "none";
68
- deferrals: number;
69
- retryAt: Date;
70
- };
71
-
72
52
  interface IWorker {
73
53
  getQueueURL(params: GetQueueURLParams): Promise<string | undefined>;
74
54
  run(params: { Records: any }, context?: object): Promise<BatchItemFailuresResponse>;