@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.
Files changed (91) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +30 -2810
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.d.ts +67 -6
  51. package/react-native.js +1 -1
  52. package/recording-transport.cjs +68 -0
  53. package/release-artifacts-build.cjs +263 -84
  54. package/release-artifacts-common.js +347 -66
  55. package/release-artifacts-debug-id.js +178 -0
  56. package/release-artifacts-inspect.js +213 -0
  57. package/release-artifacts-issue-event.js +99 -0
  58. package/release-artifacts-manifest-artifact.js +107 -0
  59. package/release-artifacts-manifest.js +77 -0
  60. package/release-artifacts-mappings.js +265 -0
  61. package/release-artifacts-prepare.js +297 -0
  62. package/release-artifacts-source-context.js +210 -0
  63. package/release-artifacts-symbolication.js +272 -421
  64. package/release-artifacts-upload-files.js +122 -0
  65. package/release-artifacts-upload-request.js +246 -0
  66. package/release-artifacts-upload.js +155 -300
  67. package/release-artifacts-urls.js +49 -0
  68. package/release-artifacts.js +189 -662
  69. package/retry-delay.cjs +32 -0
  70. package/span-validation.cjs +191 -0
  71. package/stored-events.cjs +243 -0
  72. package/types/client.d.ts +67 -0
  73. package/types/delivery.d.ts +125 -0
  74. package/types/issues.d.ts +249 -0
  75. package/types/logging.d.ts +175 -0
  76. package/types/opentelemetry.d.ts +205 -0
  77. package/types/product.d.ts +128 -0
  78. package/types/recording-transport.d.ts +15 -0
  79. package/types/sdk-error.d.ts +9 -0
  80. package/types/telemetry.d.ts +171 -0
  81. package/types/trace-context.d.ts +110 -0
  82. package/types/transport-error.d.ts +10 -0
  83. package/utf8.cjs +37 -0
  84. package/validation.cjs +49 -1
  85. package/vite-release-artifacts-config.cjs +120 -0
  86. package/vite-release-artifacts.cjs +199 -99
  87. package/vite-release-artifacts.d.cts +65 -40
  88. package/vite-release-artifacts.d.ts +65 -40
  89. package/vite-release-artifacts.js +2 -2
  90. package/winston-info.cjs +205 -0
  91. 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 };
@@ -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 };