@logbrew/sdk 0.1.38 → 0.1.40
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/automatic-delivery.cjs +271 -0
- package/batch-sender.cjs +164 -0
- package/bounded-number.cjs +34 -0
- package/browser-breadcrumbs.cjs +346 -136
- package/capture-support.cjs +100 -0
- package/client-options.cjs +292 -0
- package/client-report.cjs +232 -0
- package/client.cjs +447 -0
- package/console-arguments.cjs +151 -0
- package/console-capture.cjs +194 -0
- package/core.cjs +30 -2810
- package/delivery-health.cjs +133 -0
- package/error-causes.cjs +130 -0
- package/error-details.cjs +124 -0
- package/error-issue.cjs +187 -0
- package/event-clone.cjs +55 -0
- package/event-metadata.cjs +40 -0
- package/event-pipeline.cjs +325 -0
- package/event-queue.cjs +306 -0
- package/event-store.cjs +237 -0
- package/event-validation.cjs +159 -0
- package/examples/agent-timeline.cjs +57 -46
- package/examples/agent-timeline.mjs +41 -35
- package/examples/index.mjs +122 -94
- package/examples/readme-example.cjs +36 -27
- package/examples/readme-example.mjs +21 -17
- package/examples/real-user-smoke.cjs +41 -32
- package/examples/real-user-smoke.mjs +26 -22
- package/exception-chain.cjs +205 -0
- package/index.d.ts +144 -1076
- package/index.js +1 -1
- package/issue-exception.cjs +13 -8
- package/metric-validation.cjs +95 -0
- package/network-milestone.cjs +155 -0
- package/null-value.cjs +4 -0
- package/opentelemetry-bridge.cjs +330 -0
- package/opentelemetry-context.cjs +245 -0
- package/opentelemetry-exceptions.cjs +69 -0
- package/opentelemetry-keys.cjs +183 -0
- package/opentelemetry-span.cjs +286 -0
- package/opentelemetry-summary.cjs +213 -0
- package/opentelemetry-time.cjs +131 -0
- package/opentelemetry.cjs +99 -1068
- package/package.json +55 -1
- package/pino-destination.cjs +108 -0
- package/pino-record.cjs +237 -0
- package/product-action.cjs +163 -0
- package/provided-breadcrumbs.cjs +117 -0
- package/random-hex.cjs +29 -0
- package/react-native.d.ts +67 -6
- package/react-native.js +1 -1
- package/recording-transport.cjs +68 -0
- package/release-artifacts-build.cjs +263 -84
- package/release-artifacts-common.js +347 -66
- package/release-artifacts-debug-id.js +178 -0
- package/release-artifacts-inspect.js +213 -0
- package/release-artifacts-issue-event.js +99 -0
- package/release-artifacts-manifest-artifact.js +107 -0
- package/release-artifacts-manifest.js +77 -0
- package/release-artifacts-mappings.js +265 -0
- package/release-artifacts-prepare.js +297 -0
- package/release-artifacts-source-context.js +210 -0
- package/release-artifacts-symbolication.js +272 -421
- package/release-artifacts-upload-files.js +122 -0
- package/release-artifacts-upload-request.js +246 -0
- package/release-artifacts-upload.js +155 -300
- package/release-artifacts-urls.js +49 -0
- package/release-artifacts.js +189 -662
- package/retry-delay.cjs +32 -0
- package/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -0
- package/types/client.d.ts +67 -0
- package/types/delivery.d.ts +125 -0
- package/types/issues.d.ts +249 -0
- package/types/logging.d.ts +175 -0
- package/types/opentelemetry.d.ts +205 -0
- package/types/product.d.ts +128 -0
- package/types/recording-transport.d.ts +15 -0
- package/types/sdk-error.d.ts +9 -0
- package/types/telemetry.d.ts +171 -0
- package/types/trace-context.d.ts +110 -0
- package/types/transport-error.d.ts +10 -0
- package/utf8.cjs +37 -0
- package/validation.cjs +49 -1
- package/vite-release-artifacts-config.cjs +120 -0
- package/vite-release-artifacts.cjs +199 -99
- package/vite-release-artifacts.d.cts +65 -40
- package/vite-release-artifacts.d.ts +65 -40
- package/vite-release-artifacts.js +2 -2
- package/winston-info.cjs +205 -0
- package/winston.cjs +189 -289
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const { incrementBounded } = require("./bounded-number.cjs");
|
|
2
|
+
const { propertyOf } = require("./error-details.cjs");
|
|
3
|
+
const { automaticRetryDelayMs, rateLimitWaitMs } = require("./retry-delay.cjs");
|
|
4
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {"none" | "authentication" | "rate_limit" | "non_retryable"} PauseReason
|
|
8
|
+
* @typedef {Readonly<{ pausedReason: PauseReason, retryDelayMs: number }>} FailurePlan
|
|
9
|
+
* @typedef {Readonly<{ code: string, retryAfterMs: number | undefined, retryable: boolean | undefined }>} DeliveryFailure
|
|
10
|
+
* @typedef {Readonly<{
|
|
11
|
+
* canDeliver: () => boolean,
|
|
12
|
+
* isOpen: () => boolean,
|
|
13
|
+
* queueLength: () => number,
|
|
14
|
+
* flush: () => Promise<unknown>,
|
|
15
|
+
* serialize: (operation: () => Promise<void>) => Promise<void>,
|
|
16
|
+
* deliveryIntervalMs: number,
|
|
17
|
+
* deliveryQueueThreshold: number
|
|
18
|
+
* }>} DeliveryHost
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const NO_DELAY = 0;
|
|
22
|
+
/** @type {FailurePlan} */
|
|
23
|
+
const NON_RETRYABLE = { pausedReason: "non_retryable", retryDelayMs: NO_DELAY };
|
|
24
|
+
const NO_EVENTS = 0;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {unknown} timer
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
function unrefTimer(timer) {
|
|
31
|
+
const unref = typeof timer === "object" ? propertyOf(timer, "unref") : undefined;
|
|
32
|
+
if (typeof unref === "function") {
|
|
33
|
+
Reflect.apply(unref, timer, []);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {unknown} reason
|
|
39
|
+
* @returns {DeliveryFailure | undefined}
|
|
40
|
+
*/
|
|
41
|
+
function deliveryFailure(reason) {
|
|
42
|
+
if (!(reason instanceof SdkError)) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return { code: reason.code, retryAfterMs: reason.retryAfterMs, retryable: reason.retryable };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {DeliveryFailure} reason
|
|
50
|
+
* @param {number} retryDelayMs
|
|
51
|
+
* @returns {FailurePlan}
|
|
52
|
+
*/
|
|
53
|
+
function sdkFailurePlan(reason, retryDelayMs) {
|
|
54
|
+
if (reason.code === "unauthenticated") {
|
|
55
|
+
return { pausedReason: "authentication", retryDelayMs: NO_DELAY };
|
|
56
|
+
}
|
|
57
|
+
if (reason.code === "rate_limited") {
|
|
58
|
+
return reason.retryable === true
|
|
59
|
+
? { pausedReason: "none", retryDelayMs: rateLimitWaitMs(reason.retryAfterMs ?? retryDelayMs) }
|
|
60
|
+
: { pausedReason: "rate_limit", retryDelayMs: NO_DELAY };
|
|
61
|
+
}
|
|
62
|
+
return reason.retryable === true
|
|
63
|
+
? { pausedReason: "none", retryDelayMs }
|
|
64
|
+
: { pausedReason: "non_retryable", retryDelayMs: NO_DELAY };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class AutomaticDelivery {
|
|
68
|
+
/**
|
|
69
|
+
* @param {DeliveryHost} host
|
|
70
|
+
*/
|
|
71
|
+
constructor(host) {
|
|
72
|
+
this.host = host;
|
|
73
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
74
|
+
this.deliveryTimer = undefined;
|
|
75
|
+
this.automaticFlushActive = false;
|
|
76
|
+
this.automaticFlushPending = false;
|
|
77
|
+
/** @type {PauseReason} */
|
|
78
|
+
this.automaticPauseReason = "none";
|
|
79
|
+
this.consecutiveDeliveryFailures = 0;
|
|
80
|
+
this.retryDelayMs = 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
ready() {
|
|
87
|
+
return this.host.canDeliver() && this.automaticPauseReason === "none";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns {void}
|
|
92
|
+
*/
|
|
93
|
+
schedule() {
|
|
94
|
+
if (!this.ready()) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (this.automaticFlushActive) {
|
|
98
|
+
this.automaticFlushPending = true;
|
|
99
|
+
} else if (this.retryDelayMs > NO_DELAY) {
|
|
100
|
+
this.armTimer(this.retryDelayMs);
|
|
101
|
+
} else if (this.host.queueLength() >= this.host.deliveryQueueThreshold) {
|
|
102
|
+
this.requestFlush();
|
|
103
|
+
} else {
|
|
104
|
+
this.armTimer(this.host.deliveryIntervalMs);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {number} delayMs
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
armTimer(delayMs) {
|
|
113
|
+
if (!this.ready() || this.deliveryTimer !== undefined) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
if (this.deliveryTimer !== timer) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.deliveryTimer = undefined;
|
|
121
|
+
this.retryDelayMs = 0;
|
|
122
|
+
this.requestFlush();
|
|
123
|
+
}, delayMs);
|
|
124
|
+
this.deliveryTimer = timer;
|
|
125
|
+
unrefTimer(timer);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
131
|
+
clearTimer() {
|
|
132
|
+
if (this.deliveryTimer === undefined) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
globalThis.clearTimeout(this.deliveryTimer);
|
|
136
|
+
this.deliveryTimer = undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @returns {void}
|
|
141
|
+
*/
|
|
142
|
+
requestFlush() {
|
|
143
|
+
if (!this.ready()) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.clearTimer();
|
|
147
|
+
if (this.automaticFlushActive) {
|
|
148
|
+
this.automaticFlushPending = true;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.automaticFlushActive = true;
|
|
152
|
+
queueMicrotask(() => {
|
|
153
|
+
void this.runFlush();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @returns {Promise<void>}
|
|
159
|
+
*/
|
|
160
|
+
async runFlush() {
|
|
161
|
+
if (this.stopWhenClosed()) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await this.host.serialize(async () => {
|
|
165
|
+
let succeeded = false;
|
|
166
|
+
try {
|
|
167
|
+
await this.host.flush();
|
|
168
|
+
this.recordSuccess();
|
|
169
|
+
succeeded = true;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
this.recordFailure(error);
|
|
172
|
+
} finally {
|
|
173
|
+
this.automaticFlushActive = false;
|
|
174
|
+
this.afterFlush(succeeded);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
*/
|
|
182
|
+
stopWhenClosed() {
|
|
183
|
+
if (this.host.isOpen()) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
this.automaticFlushActive = false;
|
|
187
|
+
this.automaticFlushPending = false;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {boolean} succeeded
|
|
193
|
+
* @returns {void}
|
|
194
|
+
*/
|
|
195
|
+
afterFlush(succeeded) {
|
|
196
|
+
if (!this.host.isOpen()) {
|
|
197
|
+
this.automaticFlushPending = false;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const drainCoalesced = succeeded && this.automaticFlushPending && this.host.queueLength() > NO_EVENTS;
|
|
201
|
+
this.automaticFlushPending = false;
|
|
202
|
+
if (drainCoalesced) {
|
|
203
|
+
this.requestFlush();
|
|
204
|
+
} else if (succeeded) {
|
|
205
|
+
this.schedule();
|
|
206
|
+
} else {
|
|
207
|
+
this.resume();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @returns {void}
|
|
213
|
+
*/
|
|
214
|
+
recordSuccess() {
|
|
215
|
+
this.automaticPauseReason = "none";
|
|
216
|
+
this.consecutiveDeliveryFailures = 0;
|
|
217
|
+
this.retryDelayMs = 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {unknown} reason
|
|
222
|
+
* @returns {void}
|
|
223
|
+
*/
|
|
224
|
+
recordFailure(reason) {
|
|
225
|
+
this.consecutiveDeliveryFailures = incrementBounded(this.consecutiveDeliveryFailures);
|
|
226
|
+
this.retryDelayMs = 0;
|
|
227
|
+
const failure = deliveryFailure(reason);
|
|
228
|
+
const plan = failure === undefined ? NON_RETRYABLE : sdkFailurePlan(failure, this.backoffDelayMs(failure));
|
|
229
|
+
this.automaticPauseReason = plan.pausedReason;
|
|
230
|
+
this.retryDelayMs = plan.retryDelayMs;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @param {DeliveryFailure} reason
|
|
235
|
+
* @returns {number}
|
|
236
|
+
*/
|
|
237
|
+
backoffDelayMs(reason) {
|
|
238
|
+
const backoff = reason.code !== "unauthenticated" && reason.retryable === true
|
|
239
|
+
&& (reason.code !== "rate_limited" || reason.retryAfterMs === undefined);
|
|
240
|
+
return backoff ? automaticRetryDelayMs(this.host.deliveryIntervalMs, this.consecutiveDeliveryFailures) : NO_DELAY;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @returns {void}
|
|
245
|
+
*/
|
|
246
|
+
resume() {
|
|
247
|
+
if (this.automaticPauseReason !== "none") {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (this.retryDelayMs > NO_DELAY) {
|
|
251
|
+
this.armTimer(this.retryDelayMs);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.schedule();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @returns {Readonly<{ scheduled: boolean, coalesced: boolean, pausedReason: PauseReason, consecutiveFailures: number, retryDelayMs: number }>}
|
|
259
|
+
*/
|
|
260
|
+
health() {
|
|
261
|
+
return {
|
|
262
|
+
coalesced: this.automaticFlushPending,
|
|
263
|
+
consecutiveFailures: this.consecutiveDeliveryFailures,
|
|
264
|
+
pausedReason: this.automaticPauseReason,
|
|
265
|
+
retryDelayMs: this.retryDelayMs,
|
|
266
|
+
scheduled: this.deliveryTimer !== undefined
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = { AutomaticDelivery };
|
package/batch-sender.cjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const { incrementBounded, nextBoundedTimestamp } = require("./bounded-number.cjs");
|
|
2
|
+
const { propertyOf } = require("./error-details.cjs");
|
|
3
|
+
const { SdkError, retryAfterMsOrUndefined } = require("./sdk-error.cjs");
|
|
4
|
+
const { TransportError } = require("./transport-error.cjs");
|
|
5
|
+
const { isRecord } = require("./validation.cjs");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {"none" | "transport_error" | "invalid_response" | "network_error" | "success" | "client_error" | "server_error" | "other_status"} StatusClass
|
|
9
|
+
* @typedef {Readonly<{ send: (apiKey: string, body: string) => unknown }>} BatchTransport
|
|
10
|
+
* @typedef {Readonly<{ statusCode: number, attempts: number }>} SendResult
|
|
11
|
+
* @typedef {Readonly<{ apiKey: () => string, maxRetries: number }>} SenderConfig
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const FIRST_ATTEMPT = 1;
|
|
15
|
+
const MIN_STATUS = 100;
|
|
16
|
+
const MAX_STATUS = 599;
|
|
17
|
+
const MIN_SUCCESS = 200;
|
|
18
|
+
const MIN_REDIRECT = 300;
|
|
19
|
+
const MIN_CLIENT_ERROR = 400;
|
|
20
|
+
const MIN_SERVER_ERROR = 500;
|
|
21
|
+
const UNAUTHORIZED = 401;
|
|
22
|
+
const REQUEST_TIMEOUT = 408;
|
|
23
|
+
const TOO_MANY_REQUESTS = 429;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {number} statusCode
|
|
27
|
+
* @returns {StatusClass}
|
|
28
|
+
*/
|
|
29
|
+
function statusClass(statusCode) {
|
|
30
|
+
if (statusCode >= MIN_SUCCESS && statusCode < MIN_REDIRECT) {
|
|
31
|
+
return "success";
|
|
32
|
+
}
|
|
33
|
+
if (statusCode >= MIN_CLIENT_ERROR && statusCode < MIN_SERVER_ERROR) {
|
|
34
|
+
return "client_error";
|
|
35
|
+
}
|
|
36
|
+
return statusCode >= MIN_SERVER_ERROR ? "server_error" : "other_status";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {Readonly<Record<string, unknown>>} response
|
|
41
|
+
* @returns {number | undefined}
|
|
42
|
+
*/
|
|
43
|
+
function validStatusCode(response) {
|
|
44
|
+
const { statusCode } = response;
|
|
45
|
+
if (typeof statusCode !== "number" || !Number.isSafeInteger(statusCode)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return statusCode >= MIN_STATUS && statusCode <= MAX_STATUS ? statusCode : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {unknown} response
|
|
53
|
+
* @param {number} statusCode
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
function rejectRefusal(response, statusCode) {
|
|
57
|
+
if (statusCode === UNAUTHORIZED) {
|
|
58
|
+
throw new SdkError("unauthenticated", "transport rejected the API key");
|
|
59
|
+
}
|
|
60
|
+
if (statusCode === TOO_MANY_REQUESTS) {
|
|
61
|
+
const retryAfterMs = propertyOf(response, "retryAfterMs");
|
|
62
|
+
const errorCode = propertyOf(response, "errorCode");
|
|
63
|
+
throw new SdkError("rate_limited", "transport rate limited the batch", {
|
|
64
|
+
retryAfterMs,
|
|
65
|
+
retryable: errorCode !== "usage_limit_exceeded"
|
|
66
|
+
&& (retryAfterMsOrUndefined(retryAfterMs) !== undefined || errorCode === "rate_limited")
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class BatchSender {
|
|
72
|
+
/**
|
|
73
|
+
* @param {SenderConfig} config
|
|
74
|
+
*/
|
|
75
|
+
constructor(config) {
|
|
76
|
+
this.apiKey = config.apiKey;
|
|
77
|
+
this.maxRetries = config.maxRetries;
|
|
78
|
+
this.deliveryAttemptCount = 0;
|
|
79
|
+
this.lastAttemptAtUnixMs = 0;
|
|
80
|
+
/** @type {StatusClass} */
|
|
81
|
+
this.lastStatusClass = "none";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {BatchTransport} transport
|
|
86
|
+
* @param {string} body
|
|
87
|
+
* @returns {Promise<SendResult>}
|
|
88
|
+
*/
|
|
89
|
+
async send(transport, body) {
|
|
90
|
+
const maxAttempts = this.maxRetries + FIRST_ATTEMPT;
|
|
91
|
+
for (let attempts = FIRST_ATTEMPT; attempts <= maxAttempts; attempts += FIRST_ATTEMPT) {
|
|
92
|
+
this.beginAttempt();
|
|
93
|
+
const canRetry = attempts < maxAttempts;
|
|
94
|
+
try {
|
|
95
|
+
const statusCode = this.acceptedStatus(await transport.send(this.apiKey(), body), canRetry);
|
|
96
|
+
if (statusCode !== undefined) {
|
|
97
|
+
return { attempts, statusCode };
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.recover(error, canRetry);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new SdkError("transport_error", "exhausted retries");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
109
|
+
beginAttempt() {
|
|
110
|
+
this.deliveryAttemptCount = incrementBounded(this.deliveryAttemptCount);
|
|
111
|
+
this.lastAttemptAtUnixMs = nextBoundedTimestamp(this.lastAttemptAtUnixMs);
|
|
112
|
+
this.lastStatusClass = "transport_error";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {unknown} response
|
|
117
|
+
* @param {boolean} canRetry
|
|
118
|
+
* @returns {number | undefined}
|
|
119
|
+
*/
|
|
120
|
+
acceptedStatus(response, canRetry) {
|
|
121
|
+
const statusCode = this.classifiedStatus(response);
|
|
122
|
+
rejectRefusal(response, statusCode);
|
|
123
|
+
if (statusCode >= MIN_SUCCESS && statusCode < MIN_REDIRECT) {
|
|
124
|
+
return statusCode;
|
|
125
|
+
}
|
|
126
|
+
const retryableStatus = statusCode === REQUEST_TIMEOUT || statusCode >= MIN_SERVER_ERROR;
|
|
127
|
+
if (retryableStatus && canRetry) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
throw new SdkError("transport_error", `unexpected transport status ${statusCode}`, { retryable: retryableStatus });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {unknown} response
|
|
135
|
+
* @returns {number}
|
|
136
|
+
*/
|
|
137
|
+
classifiedStatus(response) {
|
|
138
|
+
const statusCode = isRecord(response) ? validStatusCode(response) : undefined;
|
|
139
|
+
if (statusCode === undefined) {
|
|
140
|
+
this.lastStatusClass = "invalid_response";
|
|
141
|
+
throw new SdkError("transport_error", "invalid transport response");
|
|
142
|
+
}
|
|
143
|
+
this.lastStatusClass = statusClass(statusCode);
|
|
144
|
+
return statusCode;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {unknown} reason
|
|
149
|
+
* @param {boolean} canRetry
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
recover(reason, canRetry) {
|
|
153
|
+
if (reason instanceof SdkError || !(reason instanceof TransportError)) {
|
|
154
|
+
throw reason;
|
|
155
|
+
}
|
|
156
|
+
this.lastStatusClass = "network_error";
|
|
157
|
+
if (reason.retryable && canRetry) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
throw new SdkError(reason.code, reason.message, { retryable: reason.retryable });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { BatchSender };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const STEP = 1;
|
|
2
|
+
const EPOCH_MS = 0;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {number} current
|
|
6
|
+
* @param {number} increment
|
|
7
|
+
* @returns {number}
|
|
8
|
+
*/
|
|
9
|
+
function addBounded(current, increment) {
|
|
10
|
+
return Math.min(Number.MAX_SAFE_INTEGER, current + increment);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {number} value
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
function incrementBounded(value) {
|
|
18
|
+
return value >= Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : value + STEP;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {number} previous
|
|
23
|
+
* @returns {number}
|
|
24
|
+
*/
|
|
25
|
+
function nextBoundedTimestamp(previous) {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
if (!Number.isFinite(now)) {
|
|
28
|
+
return previous;
|
|
29
|
+
}
|
|
30
|
+
const bounded = Math.min(Number.MAX_SAFE_INTEGER, Math.max(EPOCH_MS, Math.floor(now)));
|
|
31
|
+
return Math.max(previous, bounded);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { addBounded, incrementBounded, nextBoundedTimestamp };
|