@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,32 @@
1
+ const MAX_DELIVERY_INTERVAL_MS = 60_000;
2
+ const MIN_RATE_LIMIT_WAIT_MS = 1000;
3
+ const MAX_RATE_LIMIT_WAIT_MS = 3_600_000;
4
+ const MAX_BACKOFF_EXPONENT = 30;
5
+ const FIRST_FAILURE = 1;
6
+ const BACKOFF_BASE = 2;
7
+ const HALF = 2;
8
+ const INCLUSIVE = 1;
9
+ const JITTER_DIVISOR = 10;
10
+
11
+ /**
12
+ * @param {number} deliveryIntervalMs
13
+ * @param {number} consecutiveFailures
14
+ * @returns {number}
15
+ */
16
+ function automaticRetryDelayMs(deliveryIntervalMs, consecutiveFailures) {
17
+ const exponent = Math.min(consecutiveFailures - FIRST_FAILURE, MAX_BACKOFF_EXPONENT);
18
+ const maximumDelay = Math.min(MAX_DELIVERY_INTERVAL_MS, deliveryIntervalMs * (BACKOFF_BASE ** exponent));
19
+ const minimumDelay = Math.ceil(maximumDelay / HALF);
20
+ return minimumDelay + Math.floor(Math.random() * (maximumDelay - minimumDelay + INCLUSIVE));
21
+ }
22
+
23
+ /**
24
+ * @param {number} delayMs
25
+ * @returns {number}
26
+ */
27
+ function rateLimitWaitMs(delayMs) {
28
+ const bounded = Math.min(MAX_RATE_LIMIT_WAIT_MS, Math.max(MIN_RATE_LIMIT_WAIT_MS, delayMs));
29
+ return Math.min(MAX_RATE_LIMIT_WAIT_MS, bounded + Math.floor(Math.random() * (Math.floor(bounded / JITTER_DIVISOR) + INCLUSIVE)));
30
+ }
31
+
32
+ module.exports = { MAX_DELIVERY_INTERVAL_MS, automaticRetryDelayMs, rateLimitWaitMs };
@@ -0,0 +1,191 @@
1
+ const { withMetadata } = require("./event-metadata.cjs");
2
+ const { compactMetadata } = require("./log-context.cjs");
3
+ const { SdkError } = require("./sdk-error.cjs");
4
+ const { parseTraceparent } = require("./trace-context.cjs");
5
+ const {
6
+ isRecord,
7
+ optionalEntry,
8
+ requireAllowedValue,
9
+ requireNonEmpty,
10
+ requireSpanId,
11
+ requireTimestamp,
12
+ requireTraceId
13
+ } = require("./validation.cjs");
14
+
15
+ /**
16
+ * @import { Metadata, SpanAttributes, SpanEventSummary, SpanLinkSummary } from "./index.cjs"
17
+ * @import { EventAttributes } from "./event-validation.cjs"
18
+ */
19
+
20
+ const MAX_SPAN_EVENTS = 8;
21
+ const MAX_SPAN_LINKS = 8;
22
+ const NO_ENTRIES = 0;
23
+ const MIN_DURATION_MS = 0;
24
+ /** @type {ReadonlySet<SpanAttributes["status"]>} */
25
+ const SPAN_STATUSES = new Set(["ok", "error"]);
26
+
27
+ /**
28
+ * @param {unknown} metadata
29
+ * @returns {Metadata | undefined}
30
+ */
31
+ function nonEmptyMetadata(metadata) {
32
+ if (metadata === undefined) {
33
+ return undefined;
34
+ }
35
+ const compacted = compactMetadata(metadata);
36
+ return Object.keys(compacted).length > NO_ENTRIES ? compacted : undefined;
37
+ }
38
+
39
+ /**
40
+ * @param {unknown} durationMs
41
+ * @returns {number | undefined}
42
+ */
43
+ function optionalSpanDuration(durationMs) {
44
+ if (durationMs === undefined) {
45
+ return undefined;
46
+ }
47
+ if (typeof durationMs !== "number" || Number.isNaN(durationMs) || durationMs < MIN_DURATION_MS) {
48
+ throw new SdkError("validation_error", "span durationMs must be non-negative");
49
+ }
50
+ return durationMs;
51
+ }
52
+
53
+ /**
54
+ * @param {unknown} list
55
+ * @param {string} label
56
+ * @param {number} maxEntries
57
+ * @returns {readonly unknown[] | undefined}
58
+ */
59
+ function optionalBoundedList(list, label, maxEntries) {
60
+ if (list === undefined) {
61
+ return undefined;
62
+ }
63
+ if (!Array.isArray(list)) {
64
+ throw new SdkError("validation_error", `${label} must be an array`);
65
+ }
66
+ if (list.length > maxEntries) {
67
+ throw new SdkError("validation_error", `${label} must contain at most ${maxEntries} entries`);
68
+ }
69
+ return list.length === NO_ENTRIES ? undefined : list;
70
+ }
71
+
72
+ /**
73
+ * @param {unknown} event
74
+ * @returns {SpanEventSummary}
75
+ */
76
+ function validateSpanEvent(event) {
77
+ if (!isRecord(event)) {
78
+ throw new SdkError("validation_error", "span event must be an object");
79
+ }
80
+ const name = requireNonEmpty("span event name", event["name"]);
81
+ const timestamp = event["timestamp"] === undefined ? undefined : requireTimestamp(event["timestamp"]);
82
+ return {
83
+ name,
84
+ ...optionalEntry("timestamp", timestamp),
85
+ ...optionalEntry("metadata", nonEmptyMetadata(event["metadata"]))
86
+ };
87
+ }
88
+
89
+ /**
90
+ * @param {unknown} events
91
+ * @returns {SpanEventSummary[] | undefined}
92
+ */
93
+ function validateSpanEvents(events) {
94
+ return optionalBoundedList(events, "span events", MAX_SPAN_EVENTS)?.map((event) => validateSpanEvent(event));
95
+ }
96
+
97
+ /**
98
+ * @param {unknown} link
99
+ * @returns {SpanLinkSummary}
100
+ */
101
+ function validateSpanLink(link) {
102
+ if (!isRecord(link)) {
103
+ throw new SdkError("validation_error", "span link must be an object");
104
+ }
105
+ const traceId = requireTraceId(link["traceId"]);
106
+ const spanId = requireSpanId("span link spanId", link["spanId"]);
107
+ const { sampled } = link;
108
+ if (sampled !== undefined && typeof sampled !== "boolean") {
109
+ throw new SdkError("validation_error", "span link sampled must be a boolean");
110
+ }
111
+ return {
112
+ traceId: traceId.toLowerCase(),
113
+
114
+ spanId: spanId.toLowerCase(),
115
+ ...optionalEntry("sampled", sampled),
116
+ ...optionalEntry("metadata", nonEmptyMetadata(link["metadata"]))
117
+ };
118
+ }
119
+
120
+ /**
121
+ * @param {unknown} links
122
+ * @returns {SpanLinkSummary[] | undefined}
123
+ */
124
+ function validateSpanLinks(links) {
125
+ return optionalBoundedList(links, "span links", MAX_SPAN_LINKS)?.map((link) => validateSpanLink(link));
126
+ }
127
+
128
+ /**
129
+ * @param {Readonly<Record<string, unknown>>} attributes
130
+ * @returns {EventAttributes}
131
+ */
132
+ function validateSpan(attributes) {
133
+ const name = requireNonEmpty("span name", attributes["name"]);
134
+ const traceId = requireNonEmpty("span traceId", attributes["traceId"]);
135
+ const spanId = requireNonEmpty("span spanId", attributes["spanId"]);
136
+ const status = requireAllowedValue("span status", attributes["status"], SPAN_STATUSES);
137
+ const parentSpanId = attributes["parentSpanId"] === undefined
138
+ ? undefined
139
+ : requireNonEmpty("span parentSpanId", attributes["parentSpanId"]);
140
+ const durationMs = optionalSpanDuration(attributes["durationMs"]);
141
+ const events = validateSpanEvents(attributes["events"]);
142
+ const links = validateSpanLinks(attributes["links"]);
143
+ return withMetadata({
144
+ name,
145
+
146
+ traceId,
147
+
148
+ spanId,
149
+
150
+ status,
151
+ ...optionalEntry("parentSpanId", parentSpanId),
152
+ ...optionalEntry("durationMs", durationMs),
153
+ ...optionalEntry("events", events),
154
+ ...optionalEntry("links", links)
155
+ }, attributes["metadata"], attributes["context"]);
156
+ }
157
+
158
+ /**
159
+ * @param {unknown} traceparent
160
+ * @param {unknown} attributes
161
+ * @returns {SpanAttributes}
162
+ */
163
+ function spanAttributesFromTraceparent(traceparent, attributes) {
164
+ if (!isRecord(attributes)) {
165
+ throw new SdkError("validation_error", "span attributes must be an object");
166
+ }
167
+ const context = parseTraceparent(traceparent);
168
+ const name = requireNonEmpty("span name", attributes["name"]);
169
+ const spanId = requireSpanId("spanId", attributes["spanId"]);
170
+ const status = requireAllowedValue("span status", attributes["status"], SPAN_STATUSES);
171
+ const durationMs = optionalSpanDuration(attributes["durationMs"]);
172
+ const events = validateSpanEvents(attributes["events"]);
173
+ const links = validateSpanLinks(attributes["links"]);
174
+ return {
175
+ name,
176
+
177
+ traceId: context.traceId,
178
+
179
+ spanId: spanId.toLowerCase(),
180
+
181
+ parentSpanId: context.parentSpanId,
182
+
183
+ status,
184
+ ...optionalEntry("durationMs", durationMs),
185
+ ...optionalEntry("events", events),
186
+ ...optionalEntry("links", links),
187
+ ...optionalEntry("metadata", attributes["metadata"] === undefined ? undefined : compactMetadata(attributes["metadata"]))
188
+ };
189
+ }
190
+
191
+ module.exports = { spanAttributesFromTraceparent, validateSpan };
@@ -0,0 +1,243 @@
1
+ const { isObjectLike } = require("./error-details.cjs");
2
+ const { cloneEvent } = require("./event-clone.cjs");
3
+ const { EVENT_VALIDATORS, isEventType } = require("./event-validation.cjs");
4
+ const { SdkError } = require("./sdk-error.cjs");
5
+ const { utf8ByteLength } = require("./utf8.cjs");
6
+ const { isRecord, requireNonEmpty, requireTimestamp } = require("./validation.cjs");
7
+
8
+ /**
9
+ * @import { EventType, EventValidator, SdkEvent } from "./event-validation.cjs"
10
+ * @import { DeepReadonly } from "./validation.cjs"
11
+ * @typedef {{ event: DeepReadonly<SdkEvent>, eventBytes: number, serializedEvent: string }} StoredRecord
12
+ * @typedef {Readonly<Record<string, unknown>>} UnknownRecord
13
+ * @typedef {{
14
+ * attributes: UnknownRecord,
15
+ * event: UnknownRecord,
16
+ * eventBytes: number,
17
+ * header: { type: EventType, id: string, timestamp: string },
18
+ * serializedEvent: string,
19
+ * validator: EventValidator
20
+ * }} RecordParts
21
+ */
22
+
23
+ const MIN_EVENT_BYTES = 0;
24
+ const CONTEXT_SCHEMA_VERSION = 1;
25
+ const KEY_INDEX = 0;
26
+ const STEP = 1;
27
+
28
+ /**
29
+ * @returns {InstanceType<typeof SdkError>}
30
+ */
31
+ function invalidStoredRecord() {
32
+ return new SdkError("persistence_error", "event store returned an invalid record");
33
+ }
34
+
35
+ /**
36
+ * @param {string} operation
37
+ * @param {unknown} result
38
+ * @returns {unknown}
39
+ */
40
+ function requireSynchronousStoreResult(operation, result) {
41
+ if (isObjectLike(result) && typeof Reflect.get(result, "then") === "function") {
42
+ throw new SdkError("persistence_error", `eventStore.${operation} must complete synchronously`);
43
+ }
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * @param {readonly [string, unknown]} left
49
+ * @param {readonly [string, unknown]} right
50
+ * @returns {number}
51
+ */
52
+ function byKey(left, right) {
53
+ return left[KEY_INDEX].localeCompare(right[KEY_INDEX]);
54
+ }
55
+
56
+ /**
57
+ * @param {string} _key
58
+ * @param {unknown} entry
59
+ * @returns {unknown}
60
+ */
61
+ function sortedEntries(_key, entry) {
62
+ if (!isRecord(entry)) {
63
+ return entry;
64
+ }
65
+ const entries = Object.entries(entry);
66
+ entries.sort(byKey);
67
+ return Object.fromEntries(entries);
68
+ }
69
+
70
+ /**
71
+ * @param {unknown} value
72
+ * @returns {string}
73
+ */
74
+ function structuralJson(value) {
75
+ return JSON.stringify(value, sortedEntries);
76
+ }
77
+
78
+ /**
79
+ * @param {UnknownRecord} event
80
+ * @param {UnknownRecord} attributes
81
+ * @returns {{ event: UnknownRecord, attributes: UnknownRecord }}
82
+ */
83
+ function recoveredEvent(event, attributes) {
84
+ const fatalAttributes = event["type"] === "issue" && attributes["level"] === "fatal"
85
+ ? { ...attributes, level: "critical" }
86
+ : attributes;
87
+ const { context } = fatalAttributes;
88
+ const recoveredAttributes = isRecord(context) && context["schemaVersion"] === undefined
89
+ ? { ...fatalAttributes, context: { schemaVersion: CONTEXT_SCHEMA_VERSION, ...context } }
90
+ : fatalAttributes;
91
+ return { attributes: recoveredAttributes, event: { ...event, attributes: recoveredAttributes } };
92
+ }
93
+
94
+ /**
95
+ * @param {unknown} eventBytes
96
+ * @param {unknown} serializedEvent
97
+ * @returns {Readonly<{ eventBytes: number, serializedEvent: string }>}
98
+ */
99
+ function storedPayload(eventBytes, serializedEvent) {
100
+ if (typeof eventBytes !== "number" || !Number.isSafeInteger(eventBytes) || eventBytes <= MIN_EVENT_BYTES || typeof serializedEvent !== "string") {
101
+ throw invalidStoredRecord();
102
+ }
103
+ return { eventBytes, serializedEvent };
104
+ }
105
+
106
+ /**
107
+ * @param {UnknownRecord} event
108
+ * @param {Readonly<{ eventBytes: number, serializedEvent: string }>} payload
109
+ * @returns {RecordParts}
110
+ */
111
+ function storedEventParts(event, payload) {
112
+ const { type } = event;
113
+ const validator = isEventType(type) ? EVENT_VALIDATORS.get(type) : undefined;
114
+ if (!isEventType(type) || validator === undefined) {
115
+ throw invalidStoredRecord();
116
+ }
117
+ const header = {
118
+ type,
119
+
120
+ id: requireNonEmpty("event id", event["id"]),
121
+
122
+ timestamp: requireTimestamp(event["timestamp"])
123
+ };
124
+ const { attributes } = event;
125
+ if (!isRecord(attributes)) {
126
+ throw invalidStoredRecord();
127
+ }
128
+ return { ...payload, attributes, event, header, validator };
129
+ }
130
+
131
+ /**
132
+ * @param {UnknownRecord} record
133
+ * @returns {RecordParts}
134
+ */
135
+ function storedRecordParts(record) {
136
+ const { event, eventBytes, serializedEvent } = record;
137
+ if (!isRecord(event)) {
138
+ throw invalidStoredRecord();
139
+ }
140
+ return storedEventParts(event, storedPayload(eventBytes, serializedEvent));
141
+ }
142
+
143
+ /**
144
+ * @param {Readonly<RecordParts["header"]>} header
145
+ * @param {Readonly<{ event: UnknownRecord, attributes: UnknownRecord }>} recovered
146
+ * @param {EventValidator} validator
147
+ * @returns {StoredRecord}
148
+ */
149
+ function normalizedRecord(header, recovered, validator) {
150
+ /** @type {SdkEvent} */
151
+ const normalizedEvent = { ...header, attributes: validator(recovered.attributes) };
152
+ const normalizedSerializedEvent = JSON.stringify(normalizedEvent);
153
+ if (structuralJson(normalizedEvent) !== structuralJson(recovered.event)) {
154
+ throw invalidStoredRecord();
155
+ }
156
+ return {
157
+ event: cloneEvent(normalizedEvent),
158
+ eventBytes: utf8ByteLength(normalizedSerializedEvent),
159
+ serializedEvent: normalizedSerializedEvent
160
+ };
161
+ }
162
+
163
+ /**
164
+ * @param {unknown} record
165
+ * @returns {StoredRecord}
166
+ */
167
+ function normalizeRecord(record) {
168
+ if (!isRecord(record)) {
169
+ throw invalidStoredRecord();
170
+ }
171
+ const { attributes, event, eventBytes, header, serializedEvent, validator } = storedRecordParts(record);
172
+ if (JSON.stringify(event) !== serializedEvent || utf8ByteLength(serializedEvent) !== eventBytes) {
173
+ throw invalidStoredRecord();
174
+ }
175
+ return normalizedRecord(header, recoveredEvent(event, attributes), validator);
176
+ }
177
+
178
+ /**
179
+ * @param {unknown} record
180
+ * @returns {StoredRecord}
181
+ */
182
+ function normalizeStoredRecord(record) {
183
+ try {
184
+ return normalizeRecord(record);
185
+ } catch (error) {
186
+ if (error instanceof SdkError && error.code === "persistence_error") {
187
+ throw error;
188
+ }
189
+ throw invalidStoredRecord();
190
+ }
191
+ }
192
+
193
+ /**
194
+ * @param {string} serializedEvent
195
+ * @returns {unknown}
196
+ */
197
+ function parseStoredEvent(serializedEvent) {
198
+ try {
199
+ /** @type {unknown} */
200
+ const event = JSON.parse(serializedEvent);
201
+ return event;
202
+ } catch {
203
+ throw invalidStoredRecord();
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @param {unknown} serializedEvent
209
+ * @returns {DeepReadonly<SdkEvent>}
210
+ */
211
+ function restoreStoredEvent(serializedEvent) {
212
+ if (typeof serializedEvent !== "string" || serializedEvent === "") {
213
+ throw invalidStoredRecord();
214
+ }
215
+ return normalizeStoredRecord({
216
+ event: parseStoredEvent(serializedEvent),
217
+ eventBytes: utf8ByteLength(serializedEvent),
218
+ serializedEvent
219
+ }).event;
220
+ }
221
+
222
+ /**
223
+ * @param {readonly string[]} expected
224
+ * @param {readonly string[]} actual
225
+ * @returns {boolean}
226
+ */
227
+ function isOrderedSubsequence(expected, actual) {
228
+ let index = 0;
229
+ for (const value of actual) {
230
+ if (value === expected[index]) {
231
+ index += STEP;
232
+ }
233
+ }
234
+ return index === expected.length;
235
+ }
236
+
237
+ module.exports = {
238
+ invalidStoredRecord,
239
+ isOrderedSubsequence,
240
+ normalizeStoredRecord,
241
+ requireSynchronousStoreResult,
242
+ restoreStoredEvent
243
+ };
@@ -0,0 +1,67 @@
1
+ import type { ActionAttributes, EnvironmentAttributes, LogAttributes, MetricAttributes, ReleaseAttributes, SpanAttributes, TelemetryContext } from "./telemetry.js";
2
+ import type { DeliveryHealthSnapshot, DroppedEvent, EventFilter, EventStore, IssueAdmission, Transport, TransportResponse } from "./delivery.js";
3
+ import type { IssueAttributes, IssueBreadcrumbInput } from "./issues.js";
4
+
5
+ /** Buffered public client for validating, previewing, and flushing LogBrew events. */
6
+ declare class LogBrewClient {
7
+ /** Create a client from public SDK identity, retry, and API key settings. */
8
+ public static create(config: {
9
+ apiKey: string;
10
+ sdkName: string;
11
+ sdkVersion: string;
12
+ /** Versioned context merged into every captured event; event context can override dynamic fields. */
13
+ context?: TelemetryContext;
14
+ /** Retry attempts after the first send. Must be a non-negative integer; defaults to 2. */
15
+ maxRetries?: number;
16
+ eventFilter?: EventFilter;
17
+ /** Maximum queued compact event bytes. Defaults to 4 MiB. */
18
+ maxQueueBytes?: number;
19
+ maxQueueSize?: number;
20
+ /** Maximum events per request body. Defaults to 100. */
21
+ maxBatchEvents?: number;
22
+ /** Maximum UTF-8 request body bytes. Defaults to 256 KiB. */
23
+ maxBatchBytes?: number;
24
+ onEventDropped?: (drop: DroppedEvent) => void;
25
+ /** Optional app-scoped persistence adapter. Methods must complete synchronously. */
26
+ eventStore?: EventStore;
27
+ /** Client-owned transport used by automatic delivery and by flush/shutdown when no argument is supplied. */
28
+ transport?: Transport;
29
+ /** Enable interval and queue-threshold delivery. Defaults to true when transport is supplied. */
30
+ automaticDelivery?: boolean;
31
+ /** One-shot delivery interval in milliseconds. Defaults to 1000 and must not exceed 60000. */
32
+ deliveryIntervalMs?: number;
33
+ /** Queue count that triggers delivery without waiting for the interval. Defaults to min(50, maxQueueSize). */
34
+ deliveryQueueThreshold?: number;
35
+ }): LogBrewClient;
36
+ /** Return the queued event count currently buffered in memory. */
37
+ public pendingEvents(): number;
38
+ /** Return compact serialized event bytes currently buffered in memory. */
39
+ public pendingBytes(): number;
40
+ /** Return the number of events dropped because the bounded in-memory queue was full. */
41
+ public droppedEvents(): number;
42
+ /** Return a frozen, content-free snapshot of queue and delivery lifecycle health. */
43
+ public deliveryHealth(): DeliveryHealthSnapshot;
44
+ /** Return the queued event batch as stable, pretty-printed JSON. */
45
+ public previewJson(): string;
46
+ /** Purge queued events from memory and persistence while no delivery operation is active. */
47
+ public purgePendingEvents(): number;
48
+ /** Add one explicit privacy-bounded breadcrumb to the client's 64-entry issue history. */
49
+ public addBreadcrumb(breadcrumb: IssueBreadcrumbInput, timestamp?: string): void;
50
+ /** Clear the current issue breadcrumb history and return the number removed. */
51
+ public clearBreadcrumbs(): number;
52
+ public release(id: string, timestamp: string, attributes: ReleaseAttributes): void;
53
+ public environment(id: string, timestamp: string, attributes: EnvironmentAttributes): void;
54
+ public issue(id: string, timestamp: string, attributes: IssueAttributes): void;
55
+ /** Capture once and report queue admission. Custom issue overrides run once and return unavailable. */
56
+ public issueWithAdmission(id: string, timestamp: string, attributes: IssueAttributes): IssueAdmission;
57
+ public log(id: string, timestamp: string, attributes: LogAttributes): void;
58
+ public span(id: string, timestamp: string, attributes: SpanAttributes): void;
59
+ public action(id: string, timestamp: string, attributes: ActionAttributes): void;
60
+ public metric(id: string, timestamp: string, attributes: MetricAttributes): void;
61
+ /** Flush one queue snapshot in bounded batches while preserving concurrent captures and retry bodies. */
62
+ public flush(transport?: Transport): Promise<TransportResponse>;
63
+ /** Reject new capture, flush queued events, then close; a failed flush reopens the intact remainder. */
64
+ public shutdown(transport?: Transport): Promise<TransportResponse>;
65
+ }
66
+
67
+ export { LogBrewClient };
@@ -0,0 +1,125 @@
1
+ import type { ActionAttributes, EnvironmentAttributes, LogAttributes, MetricAttributes, ReleaseAttributes, SpanAttributes } from "./telemetry.js";
2
+ import type { IssueAttributes } from "./issues.js";
3
+
4
+ declare const DELIVERY_HEALTH_SCHEMA_VERSION = 1;
5
+
6
+ /** Public event union used in preview and transport payloads. */
7
+ type Event =
8
+ | { type: "release"; id: string; timestamp: string; attributes: ReleaseAttributes }
9
+ | { type: "environment"; id: string; timestamp: string; attributes: EnvironmentAttributes }
10
+ | { type: "issue"; id: string; timestamp: string; attributes: IssueAttributes }
11
+ | { type: "log"; id: string; timestamp: string; attributes: LogAttributes }
12
+ | { type: "span"; id: string; timestamp: string; attributes: SpanAttributes }
13
+ | { type: "action"; id: string; timestamp: string; attributes: ActionAttributes }
14
+ | { type: "metric"; id: string; timestamp: string; attributes: MetricAttributes };
15
+
16
+ /** Drop-only event filter called after validation and before an event is queued. */
17
+ type EventFilter = (event: Event) => boolean | void;
18
+
19
+ /** Canonical compact event record exchanged with an app-owned synchronous persistence adapter. */
20
+ type StoredEvent = {
21
+ event: Event;
22
+ serializedEvent: string;
23
+ eventBytes: number;
24
+ };
25
+
26
+ /** Explicit synchronous persistence seam used to recover and acknowledge queued events safely. */
27
+ type EventStore = {
28
+ load(): StoredEvent[];
29
+ append(record: StoredEvent): void;
30
+ acknowledge(count: number): void;
31
+ /** Removes the records at ascending `load()` indexes; without it, fatal issues cannot evict queued events. */
32
+ remove?(indexes: number[]): void;
33
+ purge(): void;
34
+ close(): void;
35
+ };
36
+
37
+ /** Queue drop notification emitted when a bounded in-memory queue is full. */
38
+ type DroppedEvent = {
39
+ reason: "event_too_large" | "evicted_for_fatal" | "queue_bytes_overflow" | "queue_overflow";
40
+ eventType: Event["type"];
41
+ eventId: string;
42
+ droppedEvents: number;
43
+ };
44
+
45
+ /** Stable transport response returned from flush and shutdown operations. */
46
+ type TransportResponse = {
47
+ /** Final HTTP-like status returned by the transport. */
48
+ statusCode: number;
49
+ /** Number of transport attempts used for the flush. */
50
+ attempts: number;
51
+ /** Number of distinct batches acknowledged; client flush/shutdown responses always include it. */
52
+ batches?: number;
53
+ /** Optional retry delay from a rate-limit response, in milliseconds. */
54
+ retryAfterMs?: number;
55
+ /** Optional `code` from a 429 response body, such as `rate_limited` or `usage_limit_exceeded`. */
56
+ errorCode?: string;
57
+ };
58
+
59
+ /** Minimal transport interface accepted by flush and shutdown operations. */
60
+ type Transport = {
61
+ send(apiKey: string, body: string): TransportResponse | Promise<TransportResponse>;
62
+ };
63
+
64
+ /** Content-free bounded delivery state with no event or sensitive transport fields. */
65
+ type DeliveryHealthSnapshot = Readonly<{
66
+ /** Stable schema discriminator for JSON consumers. */
67
+ schemaVersion: typeof DELIVERY_HEALTH_SCHEMA_VERSION;
68
+ automaticDelivery: boolean;
69
+ lifecycle: "active" | "shutting_down" | "closed";
70
+ deliveryState: "idle" | "queued" | "scheduled" | "in_flight" | "retrying" | "paused" | "accepted" | "failed" | "dropped";
71
+ storage: "memory" | "persistent";
72
+ queueEvents: number;
73
+ queueBytes: number;
74
+ /** Events and compact bytes loaded from persistence when this client started. */
75
+ hydratedEvents: number;
76
+ hydratedBytes: number;
77
+ droppedEvents: number;
78
+ droppedByReason: Readonly<{
79
+ event_too_large: number;
80
+ evicted_for_fatal: number;
81
+ queue_bytes_overflow: number;
82
+ queue_overflow: number;
83
+ }>;
84
+ lastDropReason: "none" | "event_too_large" | "evicted_for_fatal" | "queue_bytes_overflow" | "queue_overflow";
85
+ scheduled: boolean;
86
+ inFlight: boolean;
87
+ coalesced: boolean;
88
+ pendingOperations: number;
89
+ lastOutcome: "idle" | "empty" | "accepted" | "failed";
90
+ lastStatusClass: "none" | "success" | "client_error" | "server_error" | "network_error" | "transport_error" | "invalid_response" | "other_status";
91
+ pausedReason: "none" | "authentication" | "rate_limit" | "non_retryable";
92
+ consecutiveFailures: number;
93
+ /** Bounded transient retry delay; zero when no automatic retry is scheduled. */
94
+ retryDelayMs: number;
95
+ flushes: number;
96
+ failures: number;
97
+ attempts: number;
98
+ batches: number;
99
+ /** Events durably acknowledged since this client was created. */
100
+ acceptedEvents: number;
101
+ /** Bounded monotonic-within-client Unix milliseconds; zero until the transition occurs. */
102
+ lastAttemptAtUnixMs: number;
103
+ lastAcceptedAtUnixMs: number;
104
+ lastDroppedAtUnixMs: number;
105
+ }>;
106
+
107
+ /** Synchronous queue admission only; this result does not report delivery or retention. */
108
+ type IssueAdmission = Readonly<
109
+ | { status: "admitted" }
110
+ | { status: "filtered" }
111
+ | { status: "dropped"; reason: DroppedEvent["reason"] }
112
+ | { status: "unavailable" }
113
+ >;
114
+
115
+ export type {
116
+ DeliveryHealthSnapshot,
117
+ DroppedEvent,
118
+ Event,
119
+ EventFilter,
120
+ EventStore,
121
+ IssueAdmission,
122
+ StoredEvent,
123
+ Transport,
124
+ TransportResponse
125
+ };