@friggframework/core 2.0.0--canary.658.93c8e07.0 → 2.0.0--canary.656.4fb07b4.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.
@@ -1,6 +1,6 @@
1
1
  declare module "@friggframework/module-plugin" {
2
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
3
- import type { RateLimitError, RateLimitHint } from "@friggframework/errors";
3
+ import type { RateLimitHint } from "@friggframework/errors";
4
4
 
5
5
  export interface Credential {
6
6
  id?: string;
@@ -44,21 +44,12 @@ declare module "@friggframework/module-plugin" {
44
44
  };
45
45
 
46
46
  export type RateLimitPolicy = {
47
- /** The key a limit counts against. Default `entity`. */
48
47
  scope?: RateLimitScope;
49
48
  windows?: RateLimitWindow[];
50
49
  maxConcurrency?: number;
51
- /** A wait never shorter than this, for every throttled response. */
52
50
  minRetryAfterMs?: number;
53
- /** The most a request sleeps in process, in total. Default 300000. */
54
51
  maxInProcessWaitMs?: number;
55
- /** Header parsers, in order. Default all three. */
56
52
  parsers?: Array<"retryAfter" | "resetHeaders" | "ietf">;
57
- /**
58
- * Recognises a limit that is not a plain 429, or names its reason. Returns
59
- * null when the response is not a limit. A hint with no time takes its
60
- * time from the parsers or the policy.
61
- */
62
53
  classify?(
63
54
  signal: RateLimitSignal
64
55
  ): Partial<Omit<RateLimitHint, "source">> & {
@@ -75,8 +66,6 @@ declare module "@friggframework/module-plugin" {
75
66
  static requestTimeoutMs?: number;
76
67
 
77
68
  DLGT_INVALID_AUTH: string;
78
- /** Notified with the `RateLimitError` right before a wait that is too long throws. */
79
- DLGT_RATE_LIMITED: string;
80
69
  requestTimeoutMs: number;
81
70
  backOff: number[];
82
71
  fetch: any;
@@ -96,12 +85,6 @@ declare module "@friggframework/module-plugin" {
96
85
  parseBody(response: any): Promise<any>;
97
86
  refreshAuth(): Promise<any>;
98
87
  _adoptNewerCredential(): Promise<boolean>;
99
- /**
100
- * Tells the integration about a `RateLimitError` the module throws itself.
101
- * The Requester calls it for its own errors; a module built on a vendor SDK
102
- * calls it before its own throw.
103
- */
104
- _notifyRateLimited(rateLimitError: RateLimitError): Promise<void>;
105
88
 
106
89
  delegate: any;
107
90
  delegateTypes: any[];
@@ -119,7 +102,6 @@ declare module "@friggframework/module-plugin" {
119
102
  isRefreshable: boolean;
120
103
  refreshCount: number;
121
104
  DLGT_INVALID_AUTH: string;
122
- DLGT_RATE_LIMITED: string;
123
105
  fetch: any;
124
106
 
125
107
  parseBody(response: any): Promise<any>;
@@ -135,7 +117,6 @@ declare module "@friggframework/module-plugin" {
135
117
  _delete(options: RequestOptions): Promise<any>;
136
118
  refreshAuth(): Promise<any>;
137
119
  _adoptNewerCredential(): Promise<boolean>;
138
- _notifyRateLimited(rateLimitError: RateLimitError): Promise<void>;
139
120
  }
140
121
 
141
122
  type RequestOptions = {
@@ -149,6 +130,7 @@ declare module "@friggframework/module-plugin" {
149
130
  type RequesterConstructor = {
150
131
  backOff?: number[];
151
132
  fetch?: any;
133
+ random?: () => number;
152
134
  };
153
135
 
154
136
  export class ApiKeyRequester
@@ -1,24 +0,0 @@
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,85 +0,0 @@
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 };