@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,237 @@
1
+ const { isObjectLike } = require("./error-details.cjs");
2
+ const { SdkError } = require("./sdk-error.cjs");
3
+ const {
4
+ invalidStoredRecord,
5
+ normalizeStoredRecord,
6
+ requireSynchronousStoreResult,
7
+ restoreStoredEvent
8
+ } = require("./stored-events.cjs");
9
+ const { isRecord } = require("./validation.cjs");
10
+
11
+ /**
12
+ * @import { SdkEvent } from "./event-validation.cjs"
13
+ * @import { StoredRecord } from "./stored-events.cjs"
14
+ * @import { Callable, DeepReadonly } from "./validation.cjs"
15
+ * @typedef {Readonly<{
16
+ * loadClientReport: () => unknown,
17
+ * saveClientReport: (state: unknown) => unknown
18
+ * }>} ClientReportStore
19
+ * @typedef {Readonly<{
20
+ * load: () => unknown,
21
+ * append: (record: Readonly<StoredRecord>) => unknown,
22
+ * acknowledge: (count: number) => unknown,
23
+ * purge: () => unknown,
24
+ * close: () => unknown,
25
+ * remove?: (indexes: readonly number[]) => unknown,
26
+ * loadClientReport?: () => unknown,
27
+ * saveClientReport?: (state: unknown) => unknown
28
+ * }>} EventStore
29
+ * @typedef {Readonly<{
30
+ * acknowledge: (count: unknown) => unknown,
31
+ * append: (record: unknown) => unknown,
32
+ * byteCount: () => unknown,
33
+ * close: () => unknown,
34
+ * eventBytesAt: (index: number) => unknown,
35
+ * events: () => unknown,
36
+ * length: () => unknown,
37
+ * serializedAt: (index: number) => unknown,
38
+ * remove?: (indexes: readonly number[]) => unknown,
39
+ * loadClientReport?: () => unknown,
40
+ * saveClientReport?: (state: unknown) => unknown
41
+ * }>} EventQueueBackend
42
+ * @typedef {Readonly<{
43
+ * batchPrefixBytes: number,
44
+ * batchSuffixBytes: number,
45
+ * maxBatchBytes: number,
46
+ * maxQueueBytes: number,
47
+ * maxQueueSize: number
48
+ * }>} StoreLimits
49
+ */
50
+
51
+ const EVENT_STORE_METHODS = Object.freeze(["load", "append", "acknowledge", "purge", "close"]);
52
+ const EVENT_QUEUE_METHODS = Object.freeze(["acknowledge", "append", "byteCount", "close", "eventBytesAt", "events", "length", "serializedAt"]);
53
+
54
+ /**
55
+ * @param {unknown} store
56
+ * @returns {store is ClientReportStore}
57
+ */
58
+ function isClientReportStore(store) {
59
+ return isObjectLike(store)
60
+ && typeof Reflect.get(store, "loadClientReport") === "function"
61
+ && typeof Reflect.get(store, "saveClientReport") === "function";
62
+ }
63
+
64
+ /**
65
+ * @param {Readonly<Record<string, unknown>>} value
66
+ * @param {readonly string[]} methods
67
+ * @returns {string | undefined}
68
+ */
69
+ function missingMethod(value, methods) {
70
+ return methods.find((method) => typeof value[method] !== "function");
71
+ }
72
+
73
+ /**
74
+ * @param {Readonly<Record<string, unknown>>} value
75
+ * @returns {value is EventStore}
76
+ */
77
+ function isEventStore(value) {
78
+ return missingMethod(value, EVENT_STORE_METHODS) === undefined;
79
+ }
80
+
81
+ /**
82
+ * @param {unknown} eventStore
83
+ * @returns {EventStore | undefined}
84
+ */
85
+ function validateEventStore(eventStore) {
86
+ if (eventStore === undefined) {
87
+ return undefined;
88
+ }
89
+ if (!isRecord(eventStore)) {
90
+ throw new SdkError("validation_error", "eventStore must be an object");
91
+ }
92
+ const missing = missingMethod(eventStore, EVENT_STORE_METHODS);
93
+ if (missing !== undefined) {
94
+ throw new SdkError("validation_error", `eventStore.${missing} must be a function`);
95
+ }
96
+ return isEventStore(eventStore) ? eventStore : undefined;
97
+ }
98
+
99
+ /**
100
+ * @param {unknown} queue
101
+ * @returns {queue is EventQueueBackend}
102
+ */
103
+ function isEventQueueBackend(queue) {
104
+ return isRecord(queue) && missingMethod(queue, EVENT_QUEUE_METHODS) === undefined;
105
+ }
106
+
107
+ /**
108
+ * @param {EventQueueBackend} queue
109
+ * @returns {unknown[]}
110
+ */
111
+ function loadQueueRecords(queue) {
112
+ const events = queue.events();
113
+ const count = queue.length();
114
+ if (!Array.isArray(events) || events.length !== count) {
115
+ throw invalidStoredRecord();
116
+ }
117
+ /** @type {readonly unknown[]} */
118
+ const loaded = events;
119
+ return loaded.map((event, index) => ({
120
+ event,
121
+ eventBytes: queue.eventBytesAt(index),
122
+ serializedEvent: queue.serializedAt(index)
123
+ }));
124
+ }
125
+
126
+ /**
127
+ * @param {EventQueueBackend} queue
128
+ * @returns {Partial<EventStore>}
129
+ */
130
+ function optionalQueueMethods(queue) {
131
+ const { remove } = queue;
132
+ return {
133
+ ...(typeof remove === "function" ? { remove: (indexes) => queue.remove?.(indexes) } : {}),
134
+ ...(isClientReportStore(queue)
135
+ ? { loadClientReport: () => queue.loadClientReport(), saveClientReport: (state) => queue.saveClientReport(state) }
136
+ : {})
137
+ };
138
+ }
139
+
140
+ /**
141
+ * @param {Callable} eventQueueFactory
142
+ * @param {StoreLimits} limits
143
+ * @returns {EventStore}
144
+ */
145
+ function createEventStoreFromQueueFactory(eventQueueFactory, limits) {
146
+ /** @type {unknown} */
147
+ const queue = Reflect.apply(eventQueueFactory, undefined, [{ ...limits, restoreEvent: restoreStoredEvent }]);
148
+ if (!isEventQueueBackend(queue)) {
149
+ throw new SdkError("validation_error", "event queue factory returned an invalid queue");
150
+ }
151
+ return {
152
+ load: () => loadQueueRecords(queue),
153
+
154
+ append: (record) => queue.append({
155
+ event: record.event,
156
+
157
+ byteCount: record.eventBytes,
158
+
159
+ serialized: record.serializedEvent
160
+ }),
161
+
162
+ acknowledge: (count) => queue.acknowledge(count),
163
+
164
+ purge: () => queue.acknowledge(queue.length()),
165
+
166
+ close: () => queue.close(),
167
+ ...optionalQueueMethods(queue)
168
+ };
169
+ }
170
+
171
+ class RecoveredEvents {
172
+ /**
173
+ * @param {StoreLimits} limits
174
+ */
175
+ constructor(limits) {
176
+ this.limits = limits;
177
+ /** @type {DeepReadonly<SdkEvent>[]} */
178
+ this.events = [];
179
+ this.queuedEventBytes = 0;
180
+ /** @type {number[]} */
181
+ this.serializedEventBytes = [];
182
+ /** @type {string[]} */
183
+ this.serializedEvents = [];
184
+ }
185
+
186
+ /**
187
+ * @param {Readonly<StoredRecord>} normalized
188
+ * @returns {void}
189
+ */
190
+ add(normalized) {
191
+ const { limits } = this;
192
+ if (limits.batchPrefixBytes + normalized.eventBytes + limits.batchSuffixBytes > limits.maxBatchBytes) {
193
+ throw new SdkError("persistence_error", "recovered event exceeds maxBatchBytes");
194
+ }
195
+ this.queuedEventBytes += normalized.eventBytes;
196
+ if (this.queuedEventBytes > limits.maxQueueBytes) {
197
+ throw new SdkError("persistence_error", "recovered event bytes exceed maxQueueBytes");
198
+ }
199
+ this.events.push(normalized.event);
200
+ this.serializedEvents.push(normalized.serializedEvent);
201
+ this.serializedEventBytes.push(normalized.eventBytes);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * @param {EventStore} eventStore
207
+ * @param {number} maxQueueSize
208
+ * @returns {readonly unknown[]}
209
+ */
210
+ function storedRecords(eventStore, maxQueueSize) {
211
+ const records = requireSynchronousStoreResult("load", eventStore.load());
212
+ if (!Array.isArray(records)) {
213
+ throw invalidStoredRecord();
214
+ }
215
+ if (records.length > maxQueueSize) {
216
+ throw new SdkError("persistence_error", "recovered event count exceeds maxQueueSize");
217
+ }
218
+ /** @type {readonly unknown[]} */
219
+ const loaded = records;
220
+ return loaded;
221
+ }
222
+
223
+ /**
224
+ * @param {EventStore | undefined} eventStore
225
+ * @param {StoreLimits} limits
226
+ * @returns {RecoveredEvents}
227
+ */
228
+ function loadStoredEvents(eventStore, limits) {
229
+ const recovered = new RecoveredEvents(limits);
230
+ const records = eventStore === undefined ? [] : storedRecords(eventStore, limits.maxQueueSize);
231
+ for (const record of records) {
232
+ recovered.add(normalizeStoredRecord(record));
233
+ }
234
+ return recovered;
235
+ }
236
+
237
+ module.exports = { createEventStoreFromQueueFactory, isClientReportStore, loadStoredEvents, validateEventStore };
@@ -0,0 +1,159 @@
1
+ const { withMetadata } = require("./event-metadata.cjs");
2
+ const { validateIssueDiagnostics } = require("./issue-diagnostics.cjs");
3
+ const { validateIssueStackFrames, validateNativeStackFrames } = require("./issue-stack.cjs");
4
+ const { validateMetric } = require("./metric-validation.cjs");
5
+ const { validateSpan } = require("./span-validation.cjs");
6
+ const { optionalEntry, requireAllowedValue, requireNonEmpty } = require("./validation.cjs");
7
+
8
+ /**
9
+ * @import {
10
+ * ActionAttributes,
11
+ * IssueStackFrame,
12
+ * NativeStackFrame,
13
+ * Severity,
14
+ * SeverityInput,
15
+ * SpanEventSummary,
16
+ * SpanLinkSummary,
17
+ * TelemetryContext
18
+ * } from "./index.cjs"
19
+ * @import { IssueDiagnostics } from "./issue-diagnostics.cjs"
20
+ * @typedef {"release" | "environment" | "issue" | "log" | "span" | "action" | "metric"} EventType
21
+ * @typedef {IssueDiagnostics & {
22
+ * [key: string]: unknown,
23
+ * context?: TelemetryContext,
24
+ * events?: SpanEventSummary[],
25
+ * links?: SpanLinkSummary[],
26
+ * metadata?: Record<string, unknown>,
27
+ * nativeStackFrames?: NativeStackFrame[],
28
+ * stackFrames?: IssueStackFrame[]
29
+ * }} EventAttributes
30
+ * @typedef {{ type: EventType, id: string, timestamp: string, attributes: EventAttributes }} SdkEvent
31
+ * @typedef {(attributes: Readonly<Record<string, unknown>>) => EventAttributes} EventValidator
32
+ */
33
+
34
+ /** @type {ReadonlyMap<SeverityInput, Severity>} */
35
+ const SEVERITY_ALIASES = new Map([
36
+ ["trace", "info"],
37
+ ["debug", "info"],
38
+ ["info", "info"],
39
+ ["warn", "warning"],
40
+ ["warning", "warning"],
41
+ ["error", "error"],
42
+ ["fatal", "critical"],
43
+ ["critical", "critical"]
44
+ ]);
45
+ const SEVERITY_VALUES = new Set(SEVERITY_ALIASES.keys());
46
+ /** @type {ReadonlySet<ActionAttributes["status"]>} */
47
+ const ACTION_STATUSES = new Set(["queued", "running", "success", "failure"]);
48
+
49
+ /**
50
+ * @param {string} label
51
+ * @param {unknown} value
52
+ * @returns {Severity}
53
+ */
54
+ function normalizeSeverity(label, value) {
55
+ const severity = requireAllowedValue(label, value, SEVERITY_VALUES);
56
+ return SEVERITY_ALIASES.get(severity) ?? "info";
57
+ }
58
+
59
+ /**
60
+ * @param {Readonly<Record<string, unknown>>} attributes
61
+ * @returns {EventAttributes}
62
+ */
63
+ function validateRelease(attributes) {
64
+ const version = requireNonEmpty("release version", attributes["version"]);
65
+ const commit = attributes["commit"] === undefined ? undefined : requireNonEmpty("release commit", attributes["commit"]);
66
+ return withMetadata({
67
+ version,
68
+ ...optionalEntry("commit", commit),
69
+ ...optionalEntry("notes", attributes["notes"])
70
+ }, attributes["metadata"], attributes["context"]);
71
+ }
72
+
73
+ /**
74
+ * @param {Readonly<Record<string, unknown>>} attributes
75
+ * @returns {EventAttributes}
76
+ */
77
+ function validateEnvironment(attributes) {
78
+ const name = requireNonEmpty("environment name", attributes["name"]);
79
+ return withMetadata({
80
+ name,
81
+ ...optionalEntry("region", attributes["region"])
82
+ }, attributes["metadata"], attributes["context"]);
83
+ }
84
+
85
+ /**
86
+ * @param {Readonly<Record<string, unknown>>} attributes
87
+ * @returns {EventAttributes}
88
+ */
89
+ function validateIssue(attributes) {
90
+ const title = requireNonEmpty("issue title", attributes["title"]);
91
+ const level = normalizeSeverity("issue level", attributes["level"]);
92
+ const stackFrames = validateIssueStackFrames(attributes["stackFrames"]);
93
+ const nativeStackFrames = validateNativeStackFrames(attributes["nativeStackFrames"]);
94
+ return withMetadata({
95
+ title,
96
+
97
+ level,
98
+ ...optionalEntry("message", attributes["message"]),
99
+ ...optionalEntry("stackFrames", stackFrames),
100
+ ...optionalEntry("nativeStackFrames", nativeStackFrames),
101
+ ...validateIssueDiagnostics(attributes)
102
+ }, attributes["metadata"], attributes["context"]);
103
+ }
104
+
105
+ /**
106
+ * @param {Readonly<Record<string, unknown>>} attributes
107
+ * @returns {EventAttributes}
108
+ */
109
+ function validateLog(attributes) {
110
+ const message = requireNonEmpty("log message", attributes["message"]);
111
+ const level = normalizeSeverity("log level", attributes["level"]);
112
+ return withMetadata({
113
+ message,
114
+
115
+ level,
116
+ ...optionalEntry("logger", attributes["logger"])
117
+ }, attributes["metadata"], attributes["context"]);
118
+ }
119
+
120
+ /**
121
+ * @param {Readonly<Record<string, unknown>>} attributes
122
+ * @returns {EventAttributes}
123
+ */
124
+ function validateAction(attributes) {
125
+ const name = requireNonEmpty("action name", attributes["name"]);
126
+ const status = requireAllowedValue("action status", attributes["status"], ACTION_STATUSES);
127
+ return withMetadata({ name, status }, attributes["metadata"], attributes["context"]);
128
+ }
129
+
130
+ /** @type {ReadonlyMap<string, EventValidator>} */
131
+ const EVENT_VALIDATORS = new Map([
132
+ ["release", validateRelease],
133
+ ["environment", validateEnvironment],
134
+ ["issue", validateIssue],
135
+ ["log", validateLog],
136
+ ["span", validateSpan],
137
+ ["action", validateAction],
138
+ ["metric", validateMetric]
139
+ ]);
140
+
141
+ /**
142
+ * @param {unknown} type
143
+ * @returns {type is EventType}
144
+ */
145
+ function isEventType(type) {
146
+ return typeof type === "string" && EVENT_VALIDATORS.has(type);
147
+ }
148
+
149
+ module.exports = {
150
+ ACTION_STATUSES,
151
+ EVENT_VALIDATORS,
152
+ isEventType,
153
+ normalizeSeverity,
154
+ validateAction,
155
+ validateEnvironment,
156
+ validateIssue,
157
+ validateLog,
158
+ validateRelease
159
+ };
@@ -1,87 +1,98 @@
1
- let sdk;
2
- try {
3
- sdk = require("@logbrew/sdk");
4
- } catch (error) {
5
- if (error && error.code === "MODULE_NOT_FOUND") {
6
- sdk = require("../index.cjs");
7
- } else {
8
- throw error;
9
- }
10
- }
11
-
12
1
  const {
2
+ LogBrewClient,
3
+ RecordingTransport,
13
4
  createNetworkMilestoneAttributes,
14
5
  createProductActionAttributes,
15
- createTraceparentHeaders,
16
- LogBrewClient,
17
- RecordingTransport
18
- } = sdk;
6
+ createTraceparentHeaders
7
+ } = require("../index.cjs");
8
+ const { inspect } = require("node:util");
9
+ const process = require("node:process");
19
10
 
20
11
  const traceId = "4bf92f3577b34da6a3ce929d0e0e4736";
21
12
  const spanId = "b7ad6b7169203331";
22
13
  const sessionId = "sess_checkout_001";
23
14
  const timestamp = "2026-06-02T10:00:00Z";
15
+ const RETAINED_EVENTS = 2;
24
16
 
25
17
  const client = LogBrewClient.create({
26
18
  apiKey: "LOGBREW_API_KEY",
27
- sdkName: "checkout-agent-timeline",
28
- sdkVersion: "1.0.0",
29
19
  eventFilter(event) {
30
20
  return !(event.type === "log" && event.attributes.level === "info");
31
- }
21
+ },
22
+ sdkName: "checkout-agent-timeline",
23
+ sdkVersion: "1.0.0"
32
24
  });
33
25
 
34
26
  client.action("evt_checkout_started", timestamp, createProductActionAttributes({
27
+ funnel: "checkout",
28
+ metadata: {
29
+ service: "checkout",
30
+
31
+ plan: "pro"
32
+ },
35
33
  name: "checkout.started",
36
- status: "success",
37
- sessionId,
38
- traceId,
39
34
  routeTemplate: "/checkout/:step?coupon=private#payment",
40
- funnel: "checkout",
35
+ sessionId,
36
+ status: "success",
41
37
  step: "start",
42
- metadata: { service: "checkout", plan: "pro" }
38
+ traceId
43
39
  }));
44
40
 
45
41
  client.action("evt_payment_api", "2026-06-02T10:00:01Z", createNetworkMilestoneAttributes({
46
- routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
47
- method: "POST",
48
- statusCode: 503,
49
42
  durationMs: 241.5,
43
+ metadata: {
44
+ service: "payments",
45
+
46
+ retryable: true
47
+ },
48
+ method: "POST",
49
+ routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
50
50
  sessionId,
51
- traceId,
52
- metadata: { service: "payments", retryable: true }
51
+ statusCode: 503,
52
+ traceId
53
53
  }));
54
54
 
55
55
  client.log("evt_debug_noise", "2026-06-02T10:00:02Z", {
56
- message: "debug heartbeat",
57
56
  level: "info",
58
- logger: "checkout"
57
+ logger: "checkout",
58
+ message: "debug heartbeat"
59
59
  });
60
60
 
61
61
  const headers = createTraceparentHeaders({
62
- traceId,
63
62
  spanId,
64
- traceFlags: "01"
63
+ traceFlags: "01",
64
+ traceId
65
65
  });
66
66
 
67
67
  const preview = client.previewJson();
68
68
  if (preview.includes("card=private") || preview.includes("coupon=private") || preview.includes("#payment")) {
69
69
  throw new Error("agent timeline leaked query or hash metadata");
70
70
  }
71
- if (client.pendingEvents() !== 2) {
72
- throw new Error(`expected two retained timeline events, got ${client.pendingEvents()}`);
71
+ if (client.pendingEvents() !== RETAINED_EVENTS) {
72
+ throw new Error(`expected two retained timeline events, got ${String(client.pendingEvents())}`);
73
73
  }
74
74
 
75
- console.log(preview);
75
+ process.stdout.write(`${preview}\n`);
76
76
 
77
- client.shutdown(RecordingTransport.alwaysAccept()).then((response) => {
78
- console.error(JSON.stringify({
79
- ok: true,
80
- events: 2,
81
- traceparent: headers.traceparent,
82
- status: response.statusCode
83
- }));
84
- }).catch((error) => {
85
- console.error(error);
86
- process.exit(1);
87
- });
77
+ /**
78
+ * @returns {Promise<void>}
79
+ */
80
+ async function deliver() {
81
+ try {
82
+ const response = await client.shutdown(RecordingTransport.alwaysAccept());
83
+ process.stderr.write(`${JSON.stringify({
84
+ ok: true,
85
+
86
+ events: RETAINED_EVENTS,
87
+
88
+ traceparent: headers.traceparent,
89
+
90
+ status: response.statusCode
91
+ })}\n`);
92
+ } catch (error) {
93
+ process.stderr.write(`${inspect(error)}\n`);
94
+ process.exitCode = 1;
95
+ }
96
+ }
97
+
98
+ void deliver();
@@ -1,79 +1,85 @@
1
- const sdk = await import("@logbrew/sdk").catch(async (error) => {
2
- if (error && error.code === "ERR_MODULE_NOT_FOUND") {
3
- return import("../index.js");
4
- }
5
- throw error;
6
- });
7
-
8
- const {
1
+ import {
2
+ LogBrewClient,
3
+ RecordingTransport,
9
4
  createNetworkMilestoneAttributes,
10
5
  createProductActionAttributes,
11
- createTraceparentHeaders,
12
- LogBrewClient,
13
- RecordingTransport
14
- } = sdk;
6
+ createTraceparentHeaders
7
+ } from "../index.js";
8
+ import process from "node:process";
15
9
 
16
10
  const traceId = "4bf92f3577b34da6a3ce929d0e0e4736";
17
11
  const spanId = "b7ad6b7169203331";
18
12
  const sessionId = "sess_checkout_001";
19
13
  const timestamp = "2026-06-02T10:00:00Z";
14
+ const RETAINED_EVENTS = 2;
20
15
 
21
16
  const client = LogBrewClient.create({
22
17
  apiKey: "LOGBREW_API_KEY",
23
- sdkName: "checkout-agent-timeline",
24
- sdkVersion: "1.0.0",
25
18
  eventFilter(event) {
26
19
  return !(event.type === "log" && event.attributes.level === "info");
27
- }
20
+ },
21
+ sdkName: "checkout-agent-timeline",
22
+ sdkVersion: "1.0.0"
28
23
  });
29
24
 
30
25
  client.action("evt_checkout_started", timestamp, createProductActionAttributes({
26
+ funnel: "checkout",
27
+ metadata: {
28
+ service: "checkout",
29
+
30
+ plan: "pro"
31
+ },
31
32
  name: "checkout.started",
32
- status: "success",
33
- sessionId,
34
- traceId,
35
33
  routeTemplate: "/checkout/:step?coupon=private#payment",
36
- funnel: "checkout",
34
+ sessionId,
35
+ status: "success",
37
36
  step: "start",
38
- metadata: { service: "checkout", plan: "pro" }
37
+ traceId
39
38
  }));
40
39
 
41
40
  client.action("evt_payment_api", "2026-06-02T10:00:01Z", createNetworkMilestoneAttributes({
42
- routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
43
- method: "POST",
44
- statusCode: 503,
45
41
  durationMs: 241.5,
42
+ metadata: {
43
+ service: "payments",
44
+
45
+ retryable: true
46
+ },
47
+ method: "POST",
48
+ routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
46
49
  sessionId,
47
- traceId,
48
- metadata: { service: "payments", retryable: true }
50
+ statusCode: 503,
51
+ traceId
49
52
  }));
50
53
 
51
54
  client.log("evt_debug_noise", "2026-06-02T10:00:02Z", {
52
- message: "debug heartbeat",
53
55
  level: "info",
54
- logger: "checkout"
56
+ logger: "checkout",
57
+ message: "debug heartbeat"
55
58
  });
56
59
 
57
60
  const headers = createTraceparentHeaders({
58
- traceId,
59
61
  spanId,
60
- traceFlags: "01"
62
+ traceFlags: "01",
63
+ traceId
61
64
  });
62
65
 
63
66
  const preview = client.previewJson();
64
67
  if (preview.includes("card=private") || preview.includes("coupon=private") || preview.includes("#payment")) {
65
68
  throw new Error("agent timeline leaked query or hash metadata");
66
69
  }
67
- if (client.pendingEvents() !== 2) {
68
- throw new Error(`expected two retained timeline events, got ${client.pendingEvents()}`);
70
+ if (client.pendingEvents() !== RETAINED_EVENTS) {
71
+ throw new Error(`expected two retained timeline events, got ${String(client.pendingEvents())}`);
69
72
  }
70
73
 
71
- console.log(preview);
74
+ process.stdout.write(`${preview}\n`);
72
75
 
73
76
  const response = await client.shutdown(RecordingTransport.alwaysAccept());
74
- console.error(JSON.stringify({
77
+ process.stderr.write(`${JSON.stringify({
75
78
  ok: true,
76
- events: 2,
79
+
80
+ events: RETAINED_EVENTS,
81
+
77
82
  traceparent: headers.traceparent,
83
+
78
84
  status: response.statusCode
79
- }));
85
+ })}\n`);