@logbrew/sdk 0.1.38 → 0.1.39

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 (90) 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.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
@@ -0,0 +1,325 @@
1
+ const { BatchSender } = require("./batch-sender.cjs");
2
+ const { addBounded, incrementBounded, nextBoundedTimestamp } = require("./bounded-number.cjs");
3
+ const { DropLedger } = require("./client-report.cjs");
4
+ const { cloneEvent } = require("./event-clone.cjs");
5
+ const { EventQueue, isFatalEvent } = require("./event-queue.cjs");
6
+ const { isClientReportStore } = require("./event-store.cjs");
7
+ const { utf8ByteLength } = require("./utf8.cjs");
8
+ const { isCallable } = require("./validation.cjs");
9
+
10
+ /**
11
+ * @import { BatchTransport } from "./batch-sender.cjs"
12
+ * @import { DropReason } from "./client-report.cjs"
13
+ * @import { SdkEvent } from "./event-validation.cjs"
14
+ * @import { Batch, EventQueueConfig, OverflowReason } from "./event-queue.cjs"
15
+ * @import { ClientReportStore } from "./event-store.cjs"
16
+ * @import { HealthState } from "./delivery-health.cjs"
17
+ * @import { Callable, DeepReadonly } from "./validation.cjs"
18
+ * @typedef {Readonly<{ status: string, reason?: DropReason }>} EventAdmission
19
+ * @typedef {Readonly<{ eventFilter: () => unknown, onEventDropped: () => unknown, receiver: unknown }>} PipelineHooks
20
+ * @typedef {Readonly<{ statusCode: number, attempts: number, batches: number }>} FlushResult
21
+ * @typedef {"idle" | "empty" | "accepted" | "failed"} DeliveryOutcome
22
+ * @typedef {EventQueueConfig & Readonly<{
23
+ * apiKey: () => string,
24
+ * clientReportStore: ClientReportStore | undefined,
25
+ * maxBatchEvents: number,
26
+ * maxRetries: number,
27
+ * hooks: PipelineHooks
28
+ * }>} PipelineConfig
29
+ */
30
+
31
+ /**
32
+ * @param {DropReason} reason
33
+ * @returns {EventAdmission}
34
+ */
35
+ function droppedAdmission(reason) {
36
+ return Object.freeze({
37
+ status: "dropped",
38
+
39
+ reason
40
+ });
41
+ }
42
+
43
+ const EVENT_ADMISSIONS = Object.freeze({
44
+ admitted: Object.freeze({ status: "admitted" }),
45
+
46
+ filtered: Object.freeze({ status: "filtered" }),
47
+
48
+ unavailable: Object.freeze({ status: "unavailable" }),
49
+
50
+ event_too_large: droppedAdmission("event_too_large"),
51
+
52
+ queue_overflow: droppedAdmission("queue_overflow"),
53
+
54
+ queue_bytes_overflow: droppedAdmission("queue_bytes_overflow")
55
+ });
56
+ const NO_CONTENT = 204;
57
+ const NO_EVENTS = 0;
58
+ const NO_OPERATIONS = 0;
59
+ const STEP = 1;
60
+
61
+ /**
62
+ * @param {Readonly<Promise<unknown>>} operation
63
+ * @returns {Promise<boolean>}
64
+ */
65
+ async function settled(operation) {
66
+ try {
67
+ await operation;
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param {Callable} callback
76
+ * @param {unknown} receiver
77
+ * @param {unknown} argument
78
+ * @returns {boolean}
79
+ */
80
+ function notify(callback, receiver, argument) {
81
+ try {
82
+ Reflect.apply(callback, receiver, [argument]);
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ class EventPipeline {
90
+ /** @type {Promise<boolean>} */
91
+ operationTail = Promise.resolve(true);
92
+
93
+ pendingOperations = NO_OPERATIONS;
94
+
95
+ deliveryInFlight = false;
96
+
97
+ stats = {
98
+ acceptedBatchCount: NO_EVENTS,
99
+ acceptedEventCount: NO_EVENTS,
100
+ failedFlushCount: NO_EVENTS,
101
+ lastAcceptedAtUnixMs: NO_EVENTS,
102
+ successfulFlushCount: NO_EVENTS
103
+ };
104
+
105
+ /** @type {DeliveryOutcome} */
106
+ lastDeliveryOutcome = "idle";
107
+
108
+ /**
109
+ * @param {PipelineConfig} config
110
+ */
111
+ constructor(config) {
112
+ this.hooks = config.hooks;
113
+ this.maxBatchEvents = config.maxBatchEvents;
114
+ this.queue = new EventQueue(config);
115
+ const { eventStore } = this.queue;
116
+ this.ledger = new DropLedger(config.clientReportStore ?? (isClientReportStore(eventStore) ? eventStore : undefined));
117
+ this.sender = new BatchSender(config);
118
+ }
119
+
120
+ /**
121
+ * @returns {Omit<HealthState, "automaticDelivery" | "lifecycle" | "scheduled" | "coalesced" | "pausedReason" | "consecutiveFailures" | "retryDelayMs">}
122
+ */
123
+ health() {
124
+ const { ledger, queue, sender, stats } = this;
125
+ return {
126
+ acceptedEvents: stats.acceptedEventCount,
127
+ attempts: sender.deliveryAttemptCount,
128
+ batches: stats.acceptedBatchCount,
129
+ droppedByReason: ledger.droppedEventsByReason,
130
+ droppedEvents: ledger.droppedEventCount,
131
+ failures: stats.failedFlushCount,
132
+ flushes: stats.successfulFlushCount,
133
+ hydratedBytes: queue.hydrated.bytes,
134
+ hydratedEvents: queue.hydrated.events,
135
+ inFlight: this.deliveryInFlight,
136
+ lastAcceptedAtUnixMs: stats.lastAcceptedAtUnixMs,
137
+ lastAttemptAtUnixMs: sender.lastAttemptAtUnixMs,
138
+ lastDropReason: ledger.lastDropReason,
139
+ lastDroppedAtUnixMs: ledger.lastDroppedAtUnixMs,
140
+ lastOutcome: this.lastDeliveryOutcome,
141
+ lastStatusClass: sender.lastStatusClass,
142
+ pendingOperations: this.pendingOperations,
143
+ queueBytes: queue.queuedEventBytes,
144
+ queueEvents: queue.events.length,
145
+ storage: queue.eventStore === undefined ? "memory" : "persistent"
146
+ };
147
+ }
148
+
149
+ /**
150
+ * @param {DeepReadonly<SdkEvent>} event
151
+ * @returns {EventAdmission}
152
+ */
153
+ admit(event) {
154
+ const eventFilter = this.hooks.eventFilter();
155
+ if (isCallable(eventFilter) && Reflect.apply(eventFilter, this.hooks.receiver, [cloneEvent(event)]) === false) {
156
+ return EVENT_ADMISSIONS.filtered;
157
+ }
158
+ const serializedEvent = JSON.stringify(event);
159
+ const eventBytes = utf8ByteLength(serializedEvent);
160
+ const { queue } = this;
161
+ if (queue.limits.batchPrefixBytes + eventBytes + queue.limits.batchSuffixBytes > queue.limits.maxBatchBytes) {
162
+ this.recordDroppedEvent(event, "event_too_large");
163
+ return EVENT_ADMISSIONS.event_too_large;
164
+ }
165
+ return this.enqueue(event, serializedEvent, eventBytes);
166
+ }
167
+
168
+ /**
169
+ * @param {DeepReadonly<SdkEvent>} event
170
+ * @param {number} eventBytes
171
+ * @returns {Readonly<{ evicted: DeepReadonly<SdkEvent>[], overflow: OverflowReason | undefined }>}
172
+ */
173
+ makeRoom(event, eventBytes) {
174
+ const { queue } = this;
175
+ const fatal = isFatalEvent(event);
176
+ if (fatal) {
177
+ queue.synchronize();
178
+ }
179
+ const overflow = queue.overflowReason(queue.events.length, queue.queuedEventBytes + eventBytes);
180
+ return { evicted: overflow !== undefined && fatal ? queue.evictForFatal(eventBytes) : [], overflow };
181
+ }
182
+
183
+ /**
184
+ * @param {DeepReadonly<SdkEvent>} event
185
+ * @param {string} serializedEvent
186
+ * @param {number} eventBytes
187
+ * @returns {EventAdmission}
188
+ */
189
+ enqueue(event, serializedEvent, eventBytes) {
190
+ const { evicted, overflow } = this.makeRoom(event, eventBytes);
191
+ if (overflow !== undefined && evicted.length === NO_EVENTS) {
192
+ this.recordDroppedEvent(event, overflow);
193
+ return EVENT_ADMISSIONS[overflow];
194
+ }
195
+ try {
196
+ this.queue.append(event, serializedEvent, eventBytes);
197
+ } finally {
198
+ for (const evictedEvent of evicted) {
199
+ this.recordDroppedEvent(evictedEvent, "evicted_for_fatal");
200
+ }
201
+ }
202
+ return EVENT_ADMISSIONS.admitted;
203
+ }
204
+
205
+ /**
206
+ * @param {DeepReadonly<SdkEvent>} event
207
+ * @param {DropReason} reason
208
+ * @returns {boolean}
209
+ */
210
+ recordDroppedEvent(event, reason) {
211
+ this.ledger.record(reason);
212
+ const onEventDropped = this.hooks.onEventDropped();
213
+ return isCallable(onEventDropped) && notify(onEventDropped, this.hooks.receiver, {
214
+ droppedEvents: this.ledger.droppedEventCount,
215
+ eventId: event.id,
216
+ eventType: event.type,
217
+ reason
218
+ });
219
+ }
220
+
221
+ /**
222
+ * @template T
223
+ * @param {() => Promise<T>} operation
224
+ * @returns {Promise<T>}
225
+ */
226
+ async runSerialized(operation) {
227
+ this.pendingOperations = incrementBounded(this.pendingOperations);
228
+ const result = this.afterTail(operation);
229
+ this.operationTail = settled(result);
230
+ return result;
231
+ }
232
+
233
+ /**
234
+ * @template T
235
+ * @param {() => Promise<T>} operation
236
+ * @returns {Promise<T>}
237
+ */
238
+ async afterTail(operation) {
239
+ await this.operationTail;
240
+ try {
241
+ return await operation();
242
+ } finally {
243
+ this.pendingOperations = Math.max(NO_OPERATIONS, this.pendingOperations - STEP);
244
+ }
245
+ }
246
+
247
+ /**
248
+ * @param {BatchTransport} transport
249
+ * @returns {Promise<FlushResult>}
250
+ */
251
+ async flushWithHealth(transport) {
252
+ this.deliveryInFlight = true;
253
+ try {
254
+ const response = await this.flushSnapshot(transport);
255
+ this.stats.successfulFlushCount = incrementBounded(this.stats.successfulFlushCount);
256
+ this.lastDeliveryOutcome = response.batches === NO_EVENTS ? "empty" : "accepted";
257
+ return response;
258
+ } catch (error) {
259
+ this.stats.failedFlushCount = incrementBounded(this.stats.failedFlushCount);
260
+ this.lastDeliveryOutcome = "failed";
261
+ throw error;
262
+ } finally {
263
+ this.deliveryInFlight = false;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * @param {BatchTransport} transport
269
+ * @returns {Promise<FlushResult>}
270
+ */
271
+ async flushSnapshot(transport) {
272
+ this.queue.synchronize();
273
+ const totals = {
274
+ statusCode: NO_CONTENT,
275
+
276
+ attempts: 0,
277
+
278
+ batches: 0
279
+ };
280
+ for (let remainingEvents = this.queue.events.length; remainingEvents > NO_EVENTS;) {
281
+ const batch = this.currentBatch(remainingEvents);
282
+ const response = await this.sender.send(transport, batch.body);
283
+ remainingEvents -= this.settleBatch(batch);
284
+ totals.attempts += response.attempts;
285
+ totals.batches += STEP;
286
+ totals.statusCode = response.statusCode;
287
+ }
288
+ return totals;
289
+ }
290
+
291
+ /**
292
+ * @param {number} remainingEvents
293
+ * @returns {Batch}
294
+ */
295
+ currentBatch(remainingEvents) {
296
+ const { queue } = this;
297
+ const batch = queue.unacknowledgedBatch ?? Object.freeze(queue.nextBatch(
298
+ Math.min(remainingEvents, queue.events.length, this.maxBatchEvents),
299
+ this.ledger.pendingReport()
300
+ ));
301
+ queue.unacknowledgedBatch = batch;
302
+ return batch;
303
+ }
304
+
305
+ /**
306
+ * @param {Batch} batch
307
+ * @returns {number}
308
+ */
309
+ settleBatch(batch) {
310
+ this.queue.unacknowledgedBatch = undefined;
311
+ if (batch.carriesClientReport) {
312
+ this.ledger.acknowledgeReport();
313
+ }
314
+ this.queue.acknowledge(batch.eventsCount);
315
+ this.stats.acceptedEventCount = addBounded(this.stats.acceptedEventCount, batch.eventsCount);
316
+ this.stats.lastAcceptedAtUnixMs = nextBoundedTimestamp(Math.max(
317
+ this.stats.lastAcceptedAtUnixMs,
318
+ this.sender.lastAttemptAtUnixMs
319
+ ));
320
+ this.stats.acceptedBatchCount = incrementBounded(this.stats.acceptedBatchCount);
321
+ return batch.eventsCount;
322
+ }
323
+ }
324
+
325
+ module.exports = { EVENT_ADMISSIONS, EventPipeline };
@@ -0,0 +1,306 @@
1
+ const { cloneEvent } = require("./event-clone.cjs");
2
+ const { createEventStoreFromQueueFactory, loadStoredEvents } = require("./event-store.cjs");
3
+ const { SdkError } = require("./sdk-error.cjs");
4
+ const { isOrderedSubsequence, requireSynchronousStoreResult } = require("./stored-events.cjs");
5
+ const { utf8ByteLength } = require("./utf8.cjs");
6
+
7
+ /**
8
+ * @import { ClientReport } from "./client-report.cjs"
9
+ * @import { SdkEvent } from "./event-validation.cjs"
10
+ * @import { EventStore, StoreLimits } from "./event-store.cjs"
11
+ * @typedef {ReturnType<typeof loadStoredEvents>} RecoveredEvents
12
+ * @import { Callable, DeepReadonly } from "./validation.cjs"
13
+ * @typedef {Readonly<{ body: string, carriesClientReport: boolean, eventsCount: number }>} Batch
14
+ * @typedef {"queue_overflow" | "queue_bytes_overflow"} OverflowReason
15
+ * @typedef {Readonly<{
16
+ * eventStore: EventStore | undefined,
17
+ * eventQueueFactory: Callable | undefined,
18
+ * sdk: Readonly<{ name: string, language: string, version: string }>,
19
+ * maxBatchBytes: number,
20
+ * maxQueueBytes: number,
21
+ * maxQueueSize: number
22
+ * }>} EventQueueConfig
23
+ */
24
+
25
+ const BATCH_SUFFIX = "]}";
26
+ const BATCH_SUFFIX_BYTES = 2;
27
+ const PREVIEW_INDENT = 2;
28
+ const FIRST_INDEX = 0;
29
+ const SEPARATOR_BYTES = 1;
30
+ const NO_BYTES = 0;
31
+ const NO_EVENTS = 0;
32
+ const SINGLE = 1;
33
+ const PREVIOUS = 1;
34
+
35
+ /**
36
+ * @param {DeepReadonly<SdkEvent> | undefined} event
37
+ * @returns {boolean}
38
+ */
39
+ function isFatalEvent(event) {
40
+ return event?.type === "issue" && event.attributes["level"] === "critical";
41
+ }
42
+
43
+ /**
44
+ * @param {EventStore} store
45
+ * @returns {boolean}
46
+ */
47
+ function closeOwnedStore(store) {
48
+ try {
49
+ requireSynchronousStoreResult("close", store.close());
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @param {EventQueueConfig} config
58
+ * @param {StoreLimits} limits
59
+ * @returns {Readonly<{ contents: RecoveredEvents, eventStore: EventStore | undefined }>}
60
+ */
61
+ function openEventStore(config, limits) {
62
+ const { eventQueueFactory } = config;
63
+ const eventStore = eventQueueFactory === undefined
64
+ ? config.eventStore
65
+ : createEventStoreFromQueueFactory(eventQueueFactory, limits);
66
+ try {
67
+ return { contents: loadStoredEvents(eventStore, limits), eventStore };
68
+ } catch (error) {
69
+ if (eventQueueFactory !== undefined && eventStore !== undefined) {
70
+ closeOwnedStore(eventStore);
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ class EventQueue {
77
+ /**
78
+ * @param {EventQueueConfig} config
79
+ */
80
+ constructor(config) {
81
+ this.sdk = config.sdk;
82
+ this.batchPrefix = `{"sdk":${JSON.stringify(config.sdk)},"events":[`;
83
+ /** @type {StoreLimits} */
84
+ this.limits = Object.freeze({
85
+ batchPrefixBytes: utf8ByteLength(this.batchPrefix),
86
+ batchSuffixBytes: BATCH_SUFFIX_BYTES,
87
+ maxBatchBytes: config.maxBatchBytes,
88
+ maxQueueBytes: config.maxQueueBytes,
89
+ maxQueueSize: config.maxQueueSize
90
+ });
91
+ const { contents, eventStore } = openEventStore(config, this.limits);
92
+ this.eventStore = eventStore;
93
+ this.contents = contents;
94
+ this.hydrated = Object.freeze({ bytes: contents.queuedEventBytes, events: contents.events.length });
95
+ /** @type {Batch | undefined} */
96
+ this.unacknowledgedBatch = undefined;
97
+ }
98
+
99
+ /**
100
+ * @returns {DeepReadonly<SdkEvent>[]}
101
+ */
102
+ get events() {
103
+ return this.contents.events;
104
+ }
105
+
106
+ /**
107
+ * @returns {number}
108
+ */
109
+ get queuedEventBytes() {
110
+ return this.contents.queuedEventBytes;
111
+ }
112
+
113
+ /**
114
+ * @returns {void}
115
+ */
116
+ synchronize() {
117
+ if (this.eventStore === undefined) {
118
+ return;
119
+ }
120
+ const recovered = loadStoredEvents(this.eventStore, this.limits);
121
+ if (!isOrderedSubsequence(this.contents.serializedEvents, recovered.serializedEvents)) {
122
+ throw new SdkError("persistence_error", "event store lost a queued record");
123
+ }
124
+ this.contents = recovered;
125
+ }
126
+
127
+ /**
128
+ * @param {number} count
129
+ * @param {number} bytes
130
+ * @returns {OverflowReason | undefined}
131
+ */
132
+ overflowReason(count, bytes) {
133
+ if (count >= this.limits.maxQueueSize) {
134
+ return "queue_overflow";
135
+ }
136
+ return bytes > this.limits.maxQueueBytes ? "queue_bytes_overflow" : undefined;
137
+ }
138
+
139
+ /**
140
+ * @param {number} eventBytes
141
+ * @returns {number[] | undefined}
142
+ */
143
+ evictionIndexes(eventBytes) {
144
+ const indexes = [];
145
+ const { events, serializedEventBytes } = this.contents;
146
+ let count = events.length;
147
+ let bytes = this.contents.queuedEventBytes + eventBytes;
148
+ for (
149
+ let index = this.unacknowledgedBatch?.eventsCount ?? FIRST_INDEX;
150
+ index < events.length && this.overflowReason(count, bytes) !== undefined;
151
+ index += SINGLE
152
+ ) {
153
+ if (!isFatalEvent(events[index])) {
154
+ indexes.push(index);
155
+ count -= SINGLE;
156
+ bytes -= serializedEventBytes[index] ?? NO_BYTES;
157
+ }
158
+ }
159
+ return this.overflowReason(count, bytes) === undefined ? indexes : undefined;
160
+ }
161
+
162
+ /**
163
+ * @param {number} eventBytes
164
+ * @returns {DeepReadonly<SdkEvent>[]}
165
+ */
166
+ evictForFatal(eventBytes) {
167
+ const { eventStore } = this;
168
+ if (eventStore !== undefined && typeof eventStore.remove !== "function") {
169
+ return [];
170
+ }
171
+ const indexes = this.evictionIndexes(eventBytes);
172
+ if (indexes === undefined) {
173
+ return [];
174
+ }
175
+ if (eventStore !== undefined) {
176
+ requireSynchronousStoreResult("remove", eventStore.remove?.(indexes));
177
+ }
178
+ return this.removeIndexes(indexes);
179
+ }
180
+
181
+ /**
182
+ * @param {readonly number[]} indexes
183
+ * @returns {DeepReadonly<SdkEvent>[]}
184
+ */
185
+ removeIndexes(indexes) {
186
+ const evicted = indexes.map((index) => this.contents.events[index]).filter((event) => event !== undefined);
187
+ for (let position = indexes.length - PREVIOUS; position >= FIRST_INDEX; position -= SINGLE) {
188
+ this.removeAt(indexes[position] ?? FIRST_INDEX);
189
+ }
190
+ return evicted;
191
+ }
192
+
193
+ /**
194
+ * @param {number} index
195
+ * @returns {void}
196
+ */
197
+ removeAt(index) {
198
+ const { contents } = this;
199
+ contents.events.splice(index, SINGLE);
200
+ contents.serializedEvents.splice(index, SINGLE);
201
+ contents.queuedEventBytes -= contents.serializedEventBytes.splice(index, SINGLE)[FIRST_INDEX] ?? NO_BYTES;
202
+ }
203
+
204
+ /**
205
+ * @param {DeepReadonly<SdkEvent>} event
206
+ * @param {string} serializedEvent
207
+ * @param {number} eventBytes
208
+ * @returns {void}
209
+ */
210
+ append(event, serializedEvent, eventBytes) {
211
+ if (this.eventStore !== undefined) {
212
+ requireSynchronousStoreResult("append", this.eventStore.append({ event: cloneEvent(event), eventBytes, serializedEvent }));
213
+ }
214
+ const { contents } = this;
215
+ contents.events.push(event);
216
+ contents.serializedEvents.push(serializedEvent);
217
+ contents.serializedEventBytes.push(eventBytes);
218
+ contents.queuedEventBytes += eventBytes;
219
+ }
220
+
221
+ /**
222
+ * @param {number} eventsCount
223
+ * @returns {void}
224
+ */
225
+ acknowledge(eventsCount) {
226
+ if (this.eventStore !== undefined) {
227
+ requireSynchronousStoreResult("acknowledge", this.eventStore.acknowledge(eventsCount));
228
+ }
229
+ const { contents } = this;
230
+ const acknowledgedBytes = contents.serializedEventBytes.slice(FIRST_INDEX, eventsCount).reduce((sum, bytes) => sum + bytes, NO_BYTES);
231
+ contents.events.splice(FIRST_INDEX, eventsCount);
232
+ contents.serializedEvents.splice(FIRST_INDEX, eventsCount);
233
+ contents.serializedEventBytes.splice(FIRST_INDEX, eventsCount);
234
+ contents.queuedEventBytes -= acknowledgedBytes;
235
+ }
236
+
237
+ /**
238
+ * @returns {number}
239
+ */
240
+ purge() {
241
+ const { contents } = this;
242
+ const purgedEvents = contents.events.length;
243
+ if (this.eventStore !== undefined) {
244
+ requireSynchronousStoreResult("purge", this.eventStore.purge());
245
+ }
246
+ this.unacknowledgedBatch = undefined;
247
+ contents.events.splice(FIRST_INDEX);
248
+ contents.serializedEvents.splice(FIRST_INDEX);
249
+ contents.serializedEventBytes.splice(FIRST_INDEX);
250
+ contents.queuedEventBytes = 0;
251
+ return purgedEvents;
252
+ }
253
+
254
+ /**
255
+ * @returns {string}
256
+ */
257
+ previewJson() {
258
+ return JSON.stringify({
259
+ sdk: this.sdk,
260
+
261
+ events: this.contents.events
262
+ }, undefined, PREVIEW_INDENT);
263
+ }
264
+
265
+ /**
266
+ * @param {number} maxEvents
267
+ * @param {ClientReport | undefined} report
268
+ * @returns {Batch}
269
+ */
270
+ nextBatch(maxEvents, report) {
271
+ const reportSuffix = report === undefined ? undefined : `],"client_report":${JSON.stringify(report)}}`;
272
+ const reportedEvents = reportSuffix === undefined ? NO_EVENTS : this.batchEventCount(maxEvents, utf8ByteLength(reportSuffix));
273
+ const eventsCount = reportedEvents === NO_EVENTS ? this.batchEventCount(maxEvents, BATCH_SUFFIX_BYTES) : reportedEvents;
274
+ if (eventsCount === NO_EVENTS) {
275
+ throw new SdkError("transport_error", "queued event cannot fit the configured batch byte limit");
276
+ }
277
+ const suffix = reportedEvents === NO_EVENTS ? BATCH_SUFFIX : reportSuffix ?? BATCH_SUFFIX;
278
+ return {
279
+ body: `${this.batchPrefix}${this.contents.serializedEvents.slice(FIRST_INDEX, eventsCount).join(",")}${suffix}`,
280
+ carriesClientReport: reportedEvents > NO_EVENTS,
281
+ eventsCount
282
+ };
283
+ }
284
+
285
+ /**
286
+ * @param {number} maxEvents
287
+ * @param {number} suffixBytes
288
+ * @returns {number}
289
+ */
290
+ batchEventCount(maxEvents, suffixBytes) {
291
+ const { events, serializedEventBytes } = this.contents;
292
+ let bodyBytes = this.limits.batchPrefixBytes + suffixBytes;
293
+ const eventIds = new Set();
294
+ for (let index = FIRST_INDEX; index < maxEvents; index += SINGLE) {
295
+ const event = events[index];
296
+ bodyBytes += (index === FIRST_INDEX ? NO_BYTES : SEPARATOR_BYTES) + (serializedEventBytes[index] ?? NO_BYTES);
297
+ if (event === undefined || eventIds.has(event.id) || bodyBytes > this.limits.maxBatchBytes) {
298
+ return index;
299
+ }
300
+ eventIds.add(event.id);
301
+ }
302
+ return maxEvents;
303
+ }
304
+ }
305
+
306
+ module.exports = { EventQueue, isFatalEvent };