@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.
package/modules/module.js CHANGED
@@ -51,8 +51,6 @@ 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);
56
54
 
57
55
  Object.assign(this, this.definition.requiredAuthMethods);
58
56
 
@@ -162,8 +160,6 @@ class Module extends Delegate {
162
160
  await this.deauthorize();
163
161
  } else if (delegateString === this.api.DLGT_INVALID_AUTH) {
164
162
  await this.markCredentialsInvalid(object);
165
- } else if (delegateString === this.api.DLGT_RATE_LIMITED) {
166
- await this.reportRateLimit(object);
167
163
  } else if (delegateString === this.api.DLGT_CREDENTIAL_RELOAD) {
168
164
  return this.reloadCredential();
169
165
  }
@@ -238,32 +234,6 @@ class Module extends Delegate {
238
234
  }
239
235
  }
240
236
 
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
-
267
237
  async deauthorize() {
268
238
  //todo: Check if this is correct, we're instantiating a new api without params (credentials, tokens, etc...)
269
239
  this.api = new this.apiClass();
@@ -34,11 +34,6 @@ function readKeys(headers, wanted) {
34
34
  return undefined;
35
35
  }
36
36
 
37
- /**
38
- * Reads one header from a Headers-like object, a Map, an entries array or a
39
- * plain object. The name match is case-insensitive.
40
- * @returns {string|undefined}
41
- */
42
37
  function headerValue(headers, name) {
43
38
  if (!headers || typeof headers !== 'object') return undefined;
44
39
  const wanted = name.toLowerCase();
@@ -70,10 +65,6 @@ function definedOnly(fields) {
70
65
  return result;
71
66
  }
72
67
 
73
- /**
74
- * Builds a hint from a wait in ms. Returns null for a wait that is not a
75
- * finite number in [0, MAX_HINT_WAIT_MS].
76
- */
77
68
  function hintFromWait(waitMs, now, { source = 'header', ...extra } = {}) {
78
69
  if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > MAX_HINT_WAIT_MS) {
79
70
  return null;
@@ -86,9 +77,6 @@ function hintFromWait(waitMs, now, { source = 'header', ...extra } = {}) {
86
77
  };
87
78
  }
88
79
 
89
- /**
90
- * Builds a hint from an absolute time. A time in the past waits 0 ms.
91
- */
92
80
  function hintFromRetryAt(retryAt, now, { source = 'header', ...extra } = {}) {
93
81
  const at = retryAt instanceof Date ? retryAt.getTime() : Number.NaN;
94
82
  if (Number.isNaN(at)) return null;
@@ -103,9 +91,6 @@ function parseDate(text) {
103
91
  return Number.isNaN(at) ? null : new Date(at);
104
92
  }
105
93
 
106
- /**
107
- * Retry-After: delta seconds, an HTTP-date or an ISO timestamp.
108
- */
109
94
  function parseRetryAfter(value, { now = Date.now() } = {}) {
110
95
  if (isMissing(value)) return null;
111
96
  const text = String(value).trim();
@@ -145,10 +130,6 @@ function parseResetValue(text, now, extra) {
145
130
  return date ? hintFromRetryAt(date, now, extra) : null;
146
131
  }
147
132
 
148
- /**
149
- * X-RateLimit-Reset and the other reset headers. The magnitude tells the
150
- * unit: epoch milliseconds, epoch seconds, or delta seconds.
151
- */
152
133
  function parseResetHeaders(headers, { now = Date.now() } = {}) {
153
134
  const remaining = numberOrUndefined(
154
135
  firstHeader(headers, REMAINING_HEADERS)
@@ -222,11 +203,6 @@ function readKeyValue(text, key) {
222
203
  return match ? match[1] : undefined;
223
204
  }
224
205
 
225
- /**
226
- * The IETF RateLimit field: the key=value form (limit=, remaining=, reset=)
227
- * and the structured-field form ("name";r=0;t=12). The list form picks the
228
- * policy with the fewest remaining calls, then the longest wait.
229
- */
230
206
  function parseIetfRateLimit(headers, { now = Date.now() } = {}) {
231
207
  const raw = headerValue(headers, 'ratelimit');
232
208
  if (raw === undefined) return null;
@@ -259,6 +235,7 @@ const BUILT_IN_PARSERS = {
259
235
  module.exports = {
260
236
  BUILT_IN_PARSERS,
261
237
  MAX_HINT_WAIT_MS,
238
+ definedOnly,
262
239
  headerValue,
263
240
  hintFromRetryAt,
264
241
  hintFromWait,
@@ -1,5 +1,6 @@
1
1
  const {
2
2
  BUILT_IN_PARSERS,
3
+ definedOnly,
3
4
  hintFromRetryAt,
4
5
  hintFromWait,
5
6
  } = require('./parsers');
@@ -24,6 +25,7 @@ const normalizedPolicies = new WeakSet();
24
25
  const policiesByClass = new WeakMap();
25
26
 
26
27
  const isObject = (value) => value !== null && typeof value === 'object';
28
+ const isThenable = (value) => typeof value?.then === 'function';
27
29
 
28
30
  function isPlainObject(value) {
29
31
  if (!isObject(value) || Array.isArray(value)) return false;
@@ -31,14 +33,6 @@ function isPlainObject(value) {
31
33
  return prototype === Object.prototype || prototype === null;
32
34
  }
33
35
 
34
- function definedOnly(fields) {
35
- const result = {};
36
- for (const [key, value] of Object.entries(fields)) {
37
- if (value !== undefined) result[key] = value;
38
- }
39
- return result;
40
- }
41
-
42
36
  function readParsers(raw, owner) {
43
37
  if (!Array.isArray(raw.parsers)) return [...DEFAULT_PARSERS];
44
38
  for (const name of raw.parsers) {
@@ -79,11 +73,6 @@ function normalizePolicy(raw, owner) {
79
73
  return policy;
80
74
  }
81
75
 
82
- /**
83
- * Reads the static `rateLimit` policy of an API module class. Returns
84
- * undefined when the class declares none. The result is memoized per class.
85
- * Throws a TypeError for a parser name that is not built in.
86
- */
87
76
  function readRateLimitPolicy(ctor) {
88
77
  if (typeof ctor !== 'function') return undefined;
89
78
  if (policiesByClass.has(ctor)) return policiesByClass.get(ctor);
@@ -98,10 +87,6 @@ function asPolicy(value) {
98
87
  return normalizePolicy(value, 'policy');
99
88
  }
100
89
 
101
- /**
102
- * The key a limit counts against, for one requester. Undefined when the
103
- * scope needs an id that the requester does not have.
104
- */
105
90
  function computeScopeKey(policy, requester) {
106
91
  const label = requester?._telemetryModuleLabel?.() ?? 'unknown';
107
92
  const scope = policy?.scope ?? 'entity';
@@ -136,8 +121,18 @@ function computeScopeKey(policy, requester) {
136
121
  return undefined;
137
122
  }
138
123
 
124
+ function toDate(value) {
125
+ if (value === undefined || value === null) return null;
126
+ const date = value instanceof Date ? value : new Date(value);
127
+ return Number.isNaN(date.getTime()) ? null : date;
128
+ }
129
+
139
130
  function normalizeClassified(raw, now) {
140
131
  if (!isObject(raw)) return null;
132
+ const retryAt = toDate(raw.retryAt);
133
+ if (!REASONS.has(raw.reason) && !Number.isFinite(raw.waitMs) && !retryAt) {
134
+ return null;
135
+ }
141
136
 
142
137
  const extra = {
143
138
  reason: REASONS.has(raw.reason) ? raw.reason : 'unknown',
@@ -146,12 +141,7 @@ function normalizeClassified(raw, now) {
146
141
  source: CLASSIFY_SOURCES.has(raw.source) ? raw.source : 'body',
147
142
  };
148
143
 
149
- let hint = null;
150
- if (raw.retryAt !== undefined && raw.retryAt !== null) {
151
- const date =
152
- raw.retryAt instanceof Date ? raw.retryAt : new Date(raw.retryAt);
153
- hint = hintFromRetryAt(date, now, extra);
154
- }
144
+ let hint = retryAt ? hintFromRetryAt(retryAt, now, extra) : null;
155
145
  if (!hint && Number.isFinite(raw.waitMs)) {
156
146
  hint = hintFromWait(raw.waitMs, now, extra);
157
147
  }
@@ -172,6 +162,12 @@ function runClassify(policy, signal, now, onClassifyError) {
172
162
  headers: signal.headers,
173
163
  body: signal.body,
174
164
  });
165
+ if (isThenable(raw)) {
166
+ Promise.resolve(raw).catch(() => {});
167
+ throw new TypeError(
168
+ 'classify() must return a hint or null, not a Promise'
169
+ );
170
+ }
175
171
  } catch (error) {
176
172
  if (!onClassifyError) throw error;
177
173
  onClassifyError(error);
@@ -226,28 +222,12 @@ function resolveCandidates(policy, signal, { now, onClassifyError }) {
226
222
  return { hint: null, classified };
227
223
  }
228
224
 
229
- /**
230
- * Finds the hint of a throttled response: the module's classify(), then the
231
- * header parsers, then the static policy. Returns null when there is none.
232
- * A status other than 429 counts only when classify recognised it.
233
- *
234
- * @param {object} [policy] The static `rateLimit` object of an API module.
235
- * @param {{status?: number, headers?: object, body?: unknown}} signal
236
- * @param {{now?: number, onClassifyError?: (error: Error) => void}} [options]
237
- * Without `onClassifyError` an error thrown by classify propagates.
238
- */
239
225
  function classifyRateLimit(policy, signal, options = {}) {
240
226
  const { now = Date.now(), onClassifyError } = options;
241
227
  return resolveCandidates(asPolicy(policy), signal, { now, onClassifyError })
242
228
  .hint;
243
229
  }
244
230
 
245
- /**
246
- * Like classifyRateLimit, but a throttled response always gets a hint: with
247
- * none found it gets the step of the fixed backoff ladder, with source
248
- * "backoff". Null when the response is not throttled: a status other than 429
249
- * that classify did not recognise.
250
- */
251
231
  function resolveRateLimitHint(policy, signal, options = {}) {
252
232
  const {
253
233
  attempt = 0,
@@ -276,11 +256,6 @@ function resolveRateLimitHint(policy, signal, options = {}) {
276
256
  };
277
257
  }
278
258
 
279
- /**
280
- * The wait before the next attempt, for a hint that came from the provider
281
- * or the policy. Jitter only adds to the wait and never passes the budget.
282
- * `fits` is false when the wait alone is over the budget.
283
- */
284
259
  function computeWaitMs({ hint, policy, budgetMs, random = Math.random }) {
285
260
  const base = Math.max(
286
261
  hint.waitMs,
@@ -295,10 +270,6 @@ function computeWaitMs({ hint, policy, budgetMs, random = Math.random }) {
295
270
  return { waitMs: Math.min(base + jitter, budgetMs), fits: true };
296
271
  }
297
272
 
298
- /**
299
- * How long this request may still sleep in process: the cap, less what it
300
- * slept already, and the time left in the invocation, less one request.
301
- */
302
273
  function inProcessBudgetMs({
303
274
  policy,
304
275
  requestTimeoutMs = 0,
@@ -306,10 +277,8 @@ function inProcessBudgetMs({
306
277
  waitedMs = 0,
307
278
  } = {}) {
308
279
  const cap = policy?.maxInProcessWaitMs ?? DEFAULT_MAX_IN_PROCESS_WAIT_MS;
309
- return Math.max(
310
- 0,
311
- Math.min(cap - waitedMs, remainingMs - (requestTimeoutMs || 0))
312
- );
280
+ const reserveMs = Math.min(requestTimeoutMs || 0, remainingMs / 2);
281
+ return Math.max(0, Math.min(cap - waitedMs, remainingMs - reserveMs));
313
282
  }
314
283
 
315
284
  module.exports = {
@@ -12,7 +12,6 @@ const { getLoggerScope } = require('../../logs/context');
12
12
  const {
13
13
  computeScopeKey,
14
14
  computeWaitMs,
15
- headerValue,
16
15
  inProcessBudgetMs,
17
16
  readRateLimitPolicy,
18
17
  resolveRateLimitHint,
@@ -20,18 +19,6 @@ const {
20
19
 
21
20
  const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
22
21
 
23
- function isJsonMediaType(contentType) {
24
- const mediaType = String(contentType ?? '')
25
- .split(';')[0]
26
- .trim()
27
- .toLowerCase();
28
- return (
29
- mediaType === 'application/json' ||
30
- mediaType === 'text/json' ||
31
- mediaType.endsWith('+json')
32
- );
33
- }
34
-
35
22
  // A node-fetch error message holds the raw URL, and util.inspect prints the
36
23
  // cause chain, so the FetchError keeps only a sanitized copy.
37
24
  function sanitizedCause(err) {
@@ -67,8 +54,6 @@ class Requester extends Delegate {
67
54
  this._authGeneration = 0;
68
55
  this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
69
56
  this.delegateTypes.push(this.DLGT_INVALID_AUTH);
70
- this.DLGT_RATE_LIMITED = 'RATE_LIMITED';
71
- this.delegateTypes.push(this.DLGT_RATE_LIMITED);
72
57
  this.agent = get(params, 'agent', null);
73
58
 
74
59
  // Per-attempt HTTP timeout. Without this the framework called fetch()
@@ -237,9 +222,6 @@ class Requester extends Delegate {
237
222
  * @param {number} attempt - 0-based count of retries already made for
238
223
  * this call. Indexes `this.backOff` for the next delay and is passed
239
224
  * back in on each recursive retry.
240
- * @param {number} waitedMs - Milliseconds this call has slept already
241
- * for a wait that a provider or a policy set. It counts against the
242
- * in-process cap.
243
225
  */
244
226
  async _rawRequest(url, options, attempt = 0, waitedMs = 0) {
245
227
  let encodedUrl = encodeURI(url);
@@ -336,7 +318,7 @@ class Requester extends Delegate {
336
318
  status,
337
319
  attempt
338
320
  );
339
- const throttleRetry = await this._throttleRetry({
321
+ const hintedDelayMs = await this._hintedRetryDelayMs({
340
322
  throttle,
341
323
  status,
342
324
  attempt,
@@ -346,21 +328,24 @@ class Requester extends Delegate {
346
328
  response,
347
329
  timeoutMs,
348
330
  });
349
- if (throttleRetry) {
331
+ if (hintedDelayMs !== null) {
350
332
  clearRequestTimer();
351
333
  await new Promise((resolve) =>
352
- setTimeout(resolve, throttleRetry.delayMs)
334
+ setTimeout(resolve, hintedDelayMs)
353
335
  );
354
336
  return this._rawRequest(
355
337
  url,
356
338
  options,
357
339
  attempt + 1,
358
- waitedMs + throttleRetry.hintedMs
340
+ waitedMs + hintedDelayMs
359
341
  );
360
342
  }
361
343
 
362
344
  // If the status is retriable and there are back off requests left, retry the request
363
- if (status >= 500 && attempt < this.backOff.length) {
345
+ if (
346
+ (throttle?.throttled || status >= 500) &&
347
+ attempt < this.backOff.length
348
+ ) {
364
349
  clearRequestTimer();
365
350
  const delay = this.backOff[attempt] * 1000;
366
351
  await new Promise((resolve) => setTimeout(resolve, delay));
@@ -499,16 +484,7 @@ class Requester extends Delegate {
499
484
  }
500
485
  }
501
486
 
502
- /**
503
- * What to do about a throttled response: the delay before the next
504
- * attempt, or null when there is no attempt left. A wait that a hint set
505
- * and that does not fit the budget throws RateLimitError.
506
- *
507
- * @returns {Promise<{delayMs: number, hintedMs: number}|null>}
508
- * `hintedMs` is the part of the delay that a provider or a policy set,
509
- * which counts against the in-process cap.
510
- */
511
- async _throttleRetry({
487
+ async _hintedRetryDelayMs({
512
488
  throttle,
513
489
  status,
514
490
  attempt,
@@ -518,35 +494,28 @@ class Requester extends Delegate {
518
494
  response,
519
495
  timeoutMs,
520
496
  }) {
521
- if (!throttle?.throttled) return null;
522
- const { hint } = throttle;
523
- if (hint.source === 'backoff') {
524
- return attempt < this.backOff.length
525
- ? { delayMs: this.backOff[attempt] * 1000, hintedMs: 0 }
526
- : null;
497
+ if (!throttle?.throttled || throttle.hint.source === 'backoff') {
498
+ return null;
527
499
  }
528
-
529
- const budgetMs = inProcessBudgetMs({
530
- policy: this._rateLimitPolicy,
531
- requestTimeoutMs: this.requestTimeoutMs,
532
- remainingMs: remainingInvocationMs(),
533
- waitedMs,
534
- });
500
+ const { hint } = throttle;
501
+ const budgetMs = this.backOff.length
502
+ ? inProcessBudgetMs({
503
+ policy: this._rateLimitPolicy,
504
+ requestTimeoutMs: this.requestTimeoutMs,
505
+ remainingMs: remainingInvocationMs(),
506
+ waitedMs,
507
+ })
508
+ : 0;
535
509
  const { waitMs, fits } = computeWaitMs({
536
510
  hint,
537
511
  policy: this._rateLimitPolicy,
538
512
  budgetMs,
539
513
  random: this._random,
540
514
  });
541
- this._logRateLimited({
542
- status,
543
- hint,
544
- waitMs,
545
- attempt,
546
- waitedMs,
547
- action: fits ? 'wait' : 'throw',
548
- });
549
- if (fits) return { delayMs: waitMs, hintedMs: waitMs };
515
+ if (fits) {
516
+ this._logRateLimited({ status, hint, waitMs, attempt, waitedMs });
517
+ return waitMs;
518
+ }
550
519
 
551
520
  this._logRequestFailed(encodedUrl, options, status);
552
521
  const rateLimitError = await RateLimitError.create({
@@ -559,18 +528,9 @@ class Requester extends Delegate {
559
528
  module: this._telemetryModuleLabel(),
560
529
  scopeKey: computeScopeKey(this._rateLimitPolicy, this),
561
530
  });
562
- await this._notifyRateLimited(rateLimitError);
563
531
  throw this._maybeFlagTimeoutDuringBodyRead(rateLimitError, timeoutMs);
564
532
  }
565
533
 
566
- /**
567
- * Decides whether a response says a limit was hit. A 429 always does. A
568
- * 4xx or 5xx other than 401 does only when the module's classify() names
569
- * a limit. The body is read here, once, only for classify(); the text is
570
- * handed on so the error need not read the stream again.
571
- *
572
- * @returns {Promise<{throttled: boolean, hint?: object, responseBody?: string}|null>}
573
- */
574
534
  async _detectThrottle(response, status, attempt) {
575
535
  const policy = this._rateLimitPolicy;
576
536
  const canClassify =
@@ -605,9 +565,6 @@ class Requester extends Delegate {
605
565
  return {};
606
566
  }
607
567
  const text = await response.text();
608
- if (!isJsonMediaType(headerValue(response.headers, 'content-type'))) {
609
- return { text };
610
- }
611
568
  try {
612
569
  return { text, json: JSON.parse(text) };
613
570
  } catch {
@@ -615,9 +572,9 @@ class Requester extends Delegate {
615
572
  }
616
573
  }
617
574
 
618
- _logRateLimited({ status, hint, waitMs, attempt, waitedMs, action }) {
575
+ _logRateLimited({ status, hint, waitMs, attempt, waitedMs }) {
619
576
  const logger = this.logger;
620
- logger.info('Rate limited', {
577
+ logger.trace('Rate limited', {
621
578
  eventName: `${logger.name}.rate_limited`,
622
579
  statusCode: status,
623
580
  waitMs,
@@ -626,7 +583,6 @@ class Requester extends Delegate {
626
583
  hintSource: hint.source,
627
584
  attempt,
628
585
  waitedMs,
629
- action,
630
586
  });
631
587
  }
632
588
 
@@ -639,24 +595,6 @@ class Requester extends Delegate {
639
595
  });
640
596
  }
641
597
 
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
-
660
598
  _logRequestFailed(encodedUrl, options, status) {
661
599
  const logger = this.logger;
662
600
  if (!logger.isLevelEnabled('DEBUG')) return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.658.93c8e07.0",
4
+ "version": "2.0.0--canary.656.4fb07b4.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -47,9 +47,9 @@
47
47
  }
48
48
  },
49
49
  "devDependencies": {
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",
50
+ "@friggframework/eslint-config": "2.0.0--canary.656.4fb07b4.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.656.4fb07b4.0",
52
+ "@friggframework/test": "2.0.0--canary.656.4fb07b4.0",
53
53
  "@prisma/client": "^6.19.3",
54
54
  "@types/lodash": "4.17.15",
55
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -89,5 +89,5 @@
89
89
  "publishConfig": {
90
90
  "access": "public"
91
91
  },
92
- "gitHead": "93c8e078d4ff1bf00ed831152712b442abe615cd"
92
+ "gitHead": "4fb07b4e92324bbbf0d19153e2168642facc1182"
93
93
  }
@@ -58,22 +58,13 @@ declare module "@friggframework/core" {
58
58
 
59
59
  export function loadInstalledModules(): any[];
60
60
 
61
- /**
62
- * Runs `fn` with the time the invocation ends, as epoch milliseconds. A
63
- * nested scope can shorten the deadline and never extend it.
64
- */
65
61
  export function runWithInvocationDeadline<T>(
66
62
  deadlineAt: number | undefined,
67
63
  fn: () => T
68
64
  ): T;
69
65
 
70
- /** Milliseconds left in the invocation. `Infinity` outside a Lambda. */
71
66
  export function remainingInvocationMs(now?: number): number;
72
67
 
73
- /**
74
- * Finds the hint of a throttled response: the module's classify(), then the
75
- * header parsers, then the static policy. Null when there is none.
76
- */
77
68
  export function classifyRateLimit(
78
69
  policy: RateLimitPolicy | undefined,
79
70
  signal: RateLimitSignal,
@@ -16,10 +16,6 @@ declare module "@friggframework/errors" {
16
16
  readonly body: any;
17
17
  isTimeout?: boolean;
18
18
  timeoutMs?: number;
19
- /**
20
- * True when `classify` named the response as a limit but gave no time. A
21
- * `RateLimitError` always has it.
22
- */
23
19
  isRateLimited?: boolean;
24
20
  reason?: RateLimitReason;
25
21
 
@@ -35,29 +31,19 @@ declare module "@friggframework/errors" {
35
31
 
36
32
  export type RateLimitSource = "header" | "body" | "static" | "backoff";
37
33
 
38
- /** What Frigg knows about when calls are accepted again. */
39
34
  export type RateLimitHint = {
40
- /** When calls are accepted again. */
41
35
  retryAt: Date;
42
- /** Milliseconds from the time the hint was read until `retryAt`. */
43
36
  waitMs: number;
44
37
  reason: RateLimitReason;
45
- /** A provider policy name, when the response names one. */
46
38
  policy?: string;
47
- /** Calls left in the window, when the response says. */
48
39
  remaining?: number;
49
40
  source: RateLimitSource;
50
41
  };
51
42
 
52
- /**
53
- * A FetchError for a response that said a limit was hit, and for which the
54
- * response or the module's policy says when to call again.
55
- */
56
43
  export class RateLimitError extends FetchError {
57
44
  constructor(options?: RateLimitErrorConstructor);
58
45
 
59
46
  isRateLimited: true;
60
- /** When calls are accepted again. */
61
47
  retryAt: Date;
62
48
  waitMs: number;
63
49
  reason: RateLimitReason;
@@ -73,7 +59,6 @@ declare module "@friggframework/errors" {
73
59
 
74
60
  type RateLimitErrorConstructor = FetchErrorConstructor & {
75
61
  hint?: Partial<RateLimitHint>;
76
- /** Wait from `now`. When absent, `hint.retryAt` sets the time. */
77
62
  waitMs?: number;
78
63
  module?: string;
79
64
  scopeKey?: string;
@@ -1,27 +1,6 @@
1
1
  declare module "@friggframework/integrations" {
2
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
3
3
 
4
- export type IntegrationMessageAction =
5
- | { type: "RETRY_WHEN_READY" }
6
- | { type: "LINK"; label: string; url: string };
7
-
8
- /**
9
- * A stored message. The framework stores the keys of an item as given, so a
10
- * caller can add its own. A rate limit adds `code: "RATE_LIMITED"`,
11
- * `module`, `reason`, `retryAt` (ISO 8601) and `actions`.
12
- */
13
- export interface IntegrationMessage {
14
- title: string;
15
- message: string;
16
- timestamp: number;
17
- code?: string;
18
- module?: string;
19
- reason?: string;
20
- retryAt?: string;
21
- actions?: IntegrationMessageAction[];
22
- [key: string]: unknown;
23
- }
24
-
25
4
  export interface Integration {
26
5
  entities: any[];
27
6
  userId: string;
@@ -29,10 +8,10 @@ declare module "@friggframework/integrations" {
29
8
  config: any;
30
9
  version: string;
31
10
  messages: {
32
- errors: IntegrationMessage[];
33
- warnings: IntegrationMessage[];
34
- info: IntegrationMessage[];
35
- logs: IntegrationMessage[];
11
+ errors: [];
12
+ warnings: [];
13
+ info: [];
14
+ logs: [];
36
15
  };
37
16
  }
38
17