@logbrew/sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,36 +7,11 @@ Public JavaScript SDK for creating LogBrew event batches, validating them locall
7
7
  ```bash
8
8
  npm install @logbrew/sdk
9
9
  pnpm add @logbrew/sdk
10
- npm --prefix node_modules/@logbrew/sdk/examples run
11
- npm --prefix node_modules/@logbrew/sdk/examples run list
12
- npm --prefix node_modules/@logbrew/sdk/examples run help
13
- npm --prefix node_modules/@logbrew/sdk/examples run readme-example
14
- npm --prefix node_modules/@logbrew/sdk/examples run readme-example:cjs
15
- npm --prefix node_modules/@logbrew/sdk/examples run real-user-smoke
16
- npm --prefix node_modules/@logbrew/sdk/examples run real-user-smoke:cjs
17
- pnpm --dir node_modules/@logbrew/sdk/examples run
18
- pnpm --dir node_modules/@logbrew/sdk/examples run list
19
- pnpm --dir node_modules/@logbrew/sdk/examples run help
20
- pnpm --dir node_modules/@logbrew/sdk/examples run readme-example
21
- pnpm --dir node_modules/@logbrew/sdk/examples run readme-example:cjs
22
- pnpm --dir node_modules/@logbrew/sdk/examples run real-user-smoke
23
- pnpm --dir node_modules/@logbrew/sdk/examples run real-user-smoke:cjs
24
- node node_modules/@logbrew/sdk/examples/index.mjs --help
25
- node node_modules/@logbrew/sdk/examples/index.mjs --list
26
- node node_modules/@logbrew/sdk/examples/index.mjs readme-example
27
- node node_modules/@logbrew/sdk/examples/index.mjs readme-example:cjs
28
- node node_modules/@logbrew/sdk/examples/index.mjs real-user-smoke
29
- node node_modules/@logbrew/sdk/examples/index.mjs
30
- node node_modules/@logbrew/sdk/examples/index.mjs real-user-smoke:cjs
31
- node node_modules/@logbrew/sdk/examples/readme-example.mjs
32
- node node_modules/@logbrew/sdk/examples/readme-example.cjs
33
- node node_modules/@logbrew/sdk/examples/real-user-smoke.mjs
34
- node node_modules/@logbrew/sdk/examples/real-user-smoke.cjs
35
10
  ```
36
11
 
37
12
  The package supports both ESM `import` and CommonJS `require`.
38
13
  The shipped package also includes `.d.ts` and `.d.cts` declarations so ESM and CommonJS TypeScript consumers can install it directly without a separate build step.
39
- The packed tarball and installed package both ship `README.md`, package metadata, an explicit ESM/CommonJS `exports` map, runnable `examples/readme-example.mjs`, `examples/readme-example.cjs`, `examples/real-user-smoke.mjs`, and `examples/real-user-smoke.cjs` files, a tiny `examples/package.json` helper surface, a packaged `examples/index.mjs` launcher, declaration comments in `index.d.ts`, and the CommonJS `index.d.cts` declaration path that TypeScript-aware tools can inspect. Those packed and installed README, example, and declaration surfaces should still include the normal `npm` and `pnpm` install commands, the fake `LOGBREW_API_KEY` placeholder guidance, the `previewJson()` usage note, the optional `parseTraceparent()`, `createTraceparent()`, `spanAttributesFromTraceparent()`, `installLogBrewConsoleCapture()`, `createLogBrewPinoDestination()`, and `createLogBrewWinstonTransport()` guidance, the helper discovery commands, helper runtime commands, and launcher commands shown above, and the typed API docs for shapes like `ReleaseAttributes`, `SpanAttributes`, `TraceparentContext`, `TraceparentInput`, `TraceparentSpanInput`, `Event`, `ConsoleCaptureConfig`, `ConsoleCaptureHandle`, `PinoDestinationConfig`, `PinoDestinationHandle`, `PinoLogRecord`, `WinstonTransportConfig`, `WinstonTransportHandle`, `WinstonLogInfo`, `SdkError`, `TransportError`, `TransportResponse`, `alwaysAccept()`, `lastBody()`, `pendingEvents()`, `flush()`, and `shutdown()`. Real installs should also start from a package-manager-native app bootstrap, using `npm init -y` on the npm path and `pnpm init` on the pnpm path, then rewrite the temp app `package.json` dependencies plus the lockfiles so they point back to the packed `.tgz` with the expected file target and integrity metadata, while npm’s own `npm pack --dry-run --json` and `npm pack --json` outputs still report the expected tarball name, integrity, shasum, and file list, and those generated lockfiles should retain that same tarball integrity value before recreating the install through `npm ci` and `pnpm install --frozen-lockfile`. The temp app should also survive a package-manager-native removal step, with `npm uninstall @logbrew/sdk` and `pnpm remove @logbrew/sdk` dropping the SDK from `package.json`, the top-level installed-package list, and `node_modules` before the tarball is added back. The consumer graph should also stay explicit in package-manager-native output, with plain `npm ls @logbrew/sdk` and `pnpm ls @logbrew/sdk` showing the direct installed SDK tree a user reads in the terminal, `npm explain @logbrew/sdk` showing the direct root-project edge, `pnpm why @logbrew/sdk` showing the temp app dependency relationship, plain `npm list --depth=0` and `pnpm list --depth=0` showing the top-level installed dependency summary, and `npm list --json --depth=0` plus `pnpm list --json --depth=0` still listing the temp app and direct installed dependencies, while small installed-user script entries in the temp app still exercise the shipped package through `npm run` and `pnpm run`, including a TypeScript typecheck script that compiles both an ESM `.ts` consumer and a CommonJS `.cts` consumer plus a script that mirrors the published README example before and after reinstall. The installed package should also let a consumer run the shipped README-style and stronger happy-path ESM and CommonJS example files directly from `node_modules/@logbrew/sdk/examples/`, and it now ships a small Node launcher at `node node_modules/@logbrew/sdk/examples/index.mjs` so users can discover packaged examples through `--help`, list them through `--list`, run the default no-argument `real-user-smoke` path, or select named examples like `readme-example` and `real-user-smoke` without going through package-manager scripts. The helper commands in `node_modules/@logbrew/sdk/examples/package.json` should still be discoverable enough that plain `npm --prefix node_modules/@logbrew/sdk/examples run` and `pnpm --dir node_modules/@logbrew/sdk/examples run` list the available example entrypoints before users run `npm --prefix node_modules/@logbrew/sdk/examples run help`, where that help output should print copy-pasteable installed-user commands for both npm and pnpm across the README and `real-user-smoke` default, ESM, and CommonJS example paths, including `npm --prefix node_modules/@logbrew/sdk/examples run readme-example` or `pnpm --dir node_modules/@logbrew/sdk/examples run readme-example`, `npm --prefix node_modules/@logbrew/sdk/examples run readme-example:esm` or `pnpm --dir node_modules/@logbrew/sdk/examples run readme-example:esm`, `npm --prefix node_modules/@logbrew/sdk/examples run readme-example:cjs` or `pnpm --dir node_modules/@logbrew/sdk/examples run readme-example:cjs`, `npm --prefix node_modules/@logbrew/sdk/examples run real-user-smoke` or `pnpm --dir node_modules/@logbrew/sdk/examples run real-user-smoke`, `npm --prefix node_modules/@logbrew/sdk/examples run real-user-smoke:esm` or `pnpm --dir node_modules/@logbrew/sdk/examples run real-user-smoke:esm`, and `npm --prefix node_modules/@logbrew/sdk/examples run real-user-smoke:cjs` or `pnpm --dir node_modules/@logbrew/sdk/examples run real-user-smoke:cjs`, while the launcher surface itself should still reveal `node node_modules/@logbrew/sdk/examples/index.mjs --help`, `node node_modules/@logbrew/sdk/examples/index.mjs --list`, `node node_modules/@logbrew/sdk/examples/index.mjs`, `node node_modules/@logbrew/sdk/examples/index.mjs readme-example`, `node node_modules/@logbrew/sdk/examples/index.mjs real-user-smoke`, and `node node_modules/@logbrew/sdk/examples/index.mjs real-user-smoke:cjs`.
14
+ The package ships copyable examples under `node_modules/@logbrew/sdk/examples/`. Use the fake `LOGBREW_API_KEY` placeholder in docs, keep the real key in your app configuration, and call `previewJson()` when you want to inspect queued JSON before sending. Type declarations document payload shapes such as `ReleaseAttributes`, `SpanAttributes`, `MetricAttributes`, transport responses, SDK errors, lifecycle helpers, W3C trace helpers, product timeline helpers, console capture, Pino destination, and Winston transport APIs.
40
15
 
41
16
  ## Example
42
17
 
@@ -87,13 +62,30 @@ const response = await client.shutdown(transport);
87
62
  console.error(JSON.stringify({ ok: true, status: response.statusCode, attempts: response.attempts, events: 6 }));
88
63
  ```
89
64
 
65
+ ## Explicit Metrics
66
+
67
+ Use `client.metric()` when application code already knows the measurement name, value, unit, and aggregation shape. Metrics are queued like other events and are not collected automatically.
68
+
69
+ ```js
70
+ client.metric("evt_metric_001", "2026-06-02T10:00:06Z", {
71
+ name: "checkout.requests",
72
+ kind: "counter",
73
+ value: 42,
74
+ unit: "{request}",
75
+ temporality: "delta",
76
+ metadata: { service: "checkout" }
77
+ });
78
+ ```
79
+
80
+ Metric `kind` must be `counter`, `gauge`, or `histogram`. Counters and histograms must be non-negative and use `delta` or `cumulative` temporality; gauges use `instant` temporality and may be negative. Keep metric metadata primitive and low-cardinality, such as service, region, or route template.
81
+
90
82
  ## W3C Trace Context
91
83
 
92
84
  Use `parseTraceparent()`, `createTraceparent()`, and `spanAttributesFromTraceparent()` when a JavaScript service needs to continue trace context from OpenTelemetry-compatible services or pass a W3C `traceparent` value downstream.
93
85
 
94
86
  ```js
95
87
  import {
96
- createTraceparent,
88
+ createTraceparentHeaders,
97
89
  LogBrewClient,
98
90
  RecordingTransport,
99
91
  spanAttributesFromTraceparent
@@ -115,19 +107,81 @@ const span = spanAttributesFromTraceparent(incomingTraceparent, {
115
107
  });
116
108
  client.span("evt_checkout_span", "2026-06-02T10:00:04Z", span);
117
109
 
118
- const downstreamTraceparent = createTraceparent({
119
- traceId: span.traceId,
120
- spanId: span.spanId,
121
- traceFlags: "01"
122
- });
123
110
  await fetch("https://example.invalid/payments", {
124
- headers: { traceparent: downstreamTraceparent }
111
+ headers: createTraceparentHeaders({
112
+ traceId: span.traceId,
113
+ spanId: span.spanId,
114
+ traceFlags: "01"
115
+ })
125
116
  });
126
117
 
127
118
  await client.flush(RecordingTransport.alwaysAccept());
128
119
  ```
129
120
 
130
- The helpers validate the W3C `version-traceId-parentSpanId-traceFlags` shape, reject all-zero trace/span ids, normalize valid ids to lowercase, expose the sampled flag from `traceFlags`, and keep span metadata primitive-only. They do not install OpenTelemetry or patch HTTP clients; use them when you need explicit interop in code you own.
121
+ The helpers validate the W3C `version-traceId-parentSpanId-traceFlags` shape, reject all-zero trace/span ids, normalize valid ids to lowercase, expose the sampled flag from `traceFlags`, and keep span metadata primitive-only. `createTraceparentHeaders()` returns an explicit outbound carrier with only `traceparent`. The helpers do not install OpenTelemetry or patch HTTP clients; use them when you need explicit interop in code you own.
122
+
123
+ LogBrew severity categories are `info`, `warning`, `error`, and `critical`. The JavaScript SDK accepts common runtime aliases such as `trace`, `debug`, `warn`, and `fatal` for compatibility, then serializes canonical values before queued events are sent. The shared mapping is documented in the [LogBrew severity contract](../../docs/severity-contract.md).
124
+
125
+ ## Event Filtering
126
+
127
+ Use `eventFilter` when your app needs a last-mile privacy or sampling gate before events enter the in-memory queue. The filter receives a copy of the already validated event, so severity aliases are already canonical and mutations inside the callback do not alter queued payloads. Return `false` to drop an event; return `true` or nothing to keep it.
128
+
129
+ ```js
130
+ const client = LogBrewClient.create({
131
+ apiKey: "LOGBREW_API_KEY",
132
+ sdkName: "checkout-api",
133
+ sdkVersion: "1.0.0",
134
+ eventFilter(event) {
135
+ if (event.type === "log" && event.attributes.level === "info") {
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+ });
141
+ ```
142
+
143
+ Prefer removing sensitive values at the source before calling LogBrew. `eventFilter` is intentionally drop-only: it avoids broad mutable event processing, global scopes, and hidden context that can make observability payloads harder to reason about.
144
+
145
+ ## Agent-Readable Timelines
146
+
147
+ Use `createProductActionAttributes()` and `createNetworkMilestoneAttributes()` when a service already knows important product steps or API milestones. The helpers create normal `action` event attributes with primitive metadata that can be analyzed across many sessions without visual replay, global HTTP patching, payload capture, or header capture.
148
+
149
+ ```js
150
+ import {
151
+ createNetworkMilestoneAttributes,
152
+ createProductActionAttributes,
153
+ LogBrewClient
154
+ } from "@logbrew/sdk";
155
+
156
+ const client = LogBrewClient.create({
157
+ apiKey: "LOGBREW_API_KEY",
158
+ sdkName: "checkout-api",
159
+ sdkVersion: "1.0.0"
160
+ });
161
+
162
+ client.action("evt_checkout_submit", new Date().toISOString(), createProductActionAttributes({
163
+ name: "checkout.submit",
164
+ status: "running",
165
+ sessionId: "sess_123",
166
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
167
+ routeTemplate: "/checkout/:step",
168
+ funnel: "checkout",
169
+ step: "submit",
170
+ metadata: { service: "checkout" }
171
+ }));
172
+
173
+ client.action("evt_payment_api", new Date().toISOString(), createNetworkMilestoneAttributes({
174
+ routeTemplate: "/payments/:id",
175
+ method: "POST",
176
+ statusCode: 202,
177
+ durationMs: 94,
178
+ sessionId: "sess_123",
179
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
180
+ metadata: { service: "checkout" }
181
+ }));
182
+ ```
183
+
184
+ Timeline helpers keep only primitive metadata, strip query strings and hashes from route templates, normalize HTTP methods, infer failed network milestones from status codes `400` and above, and serialize through the existing `action` event type. Keep metadata low-cardinality, such as `sessionId`, `traceId`, `routeTemplate`, `method`, `statusCode`, `durationMs`, `screen`, `funnel`, and `step`.
131
185
 
132
186
  ## Console Capture
133
187
 
@@ -186,7 +240,7 @@ logger.error(new Error("payment failed"), "checkout failed");
186
240
  await destination.flush();
187
241
  ```
188
242
 
189
- The Pino adapter reads JSON log lines, maps `trace`/`debug`/`info`/`warn`/`error`/`fatal` into LogBrew levels, captures primitive Pino fields as `context.*`, captures serialized error name/message, skips noisy runtime defaults, and omits stack text unless `includeErrorStack: true` is set. It does not patch Pino or replace application logger ownership.
243
+ The Pino adapter reads JSON log lines, maps Pino `trace`/`debug` to LogBrew `info`, `warn` to `warning`, `error` to `error`, and `fatal` to `critical`, captures primitive Pino fields as `context.*`, captures serialized error name/message, skips noisy runtime defaults, and omits stack text unless `includeErrorStack: true` is set. It does not patch Pino or replace application logger ownership.
190
244
 
191
245
  ## Winston Transport
192
246
 
@@ -220,6 +274,6 @@ logger.error(new Error("payment failed"));
220
274
  await logbrewTransport.flush();
221
275
  ```
222
276
 
223
- The Winston adapter receives Winston `info` objects, maps `debug`/`silly` to `debug`, `warn` to `warning`, `error`/`fatal` to `error`, and other common Winston levels to `info`. It captures primitive info fields as `context.*`, captures nested `err`/`error` objects or formatted error stack name/message, omits stack text unless `includeErrorStack: true` is set, and exposes `onError` for capture failures. It does not mutate Winston globals or replace the app's logger.
277
+ The Winston adapter receives Winston `info` objects, maps `debug`/`silly` to LogBrew `info`, `warn` to `warning`, `error` to `error`, `fatal`/`critical` to `critical`, and other common Winston levels to `info`. It captures primitive info fields as `context.*`, captures nested `err`/`error` objects or formatted error stack name/message, omits stack text unless `includeErrorStack: true` is set, and exposes `onError` for capture failures. It does not mutate Winston globals or replace the app's logger.
224
278
 
225
- Use a clearly fake placeholder like `LOGBREW_API_KEY` in local examples and tests. Call `flush` or `shutdown` to send queued events through a transport, and use `previewJson()` when you want a stable local JSON preview without sending anything.
279
+ Use a clearly fake placeholder like `LOGBREW_API_KEY` in examples. Call `flush` or `shutdown` to send queued events through a transport, and use `previewJson()` when you want a stable local JSON preview before sending anything.
@@ -9,7 +9,16 @@ try {
9
9
  }
10
10
  }
11
11
 
12
- const { LogBrewClient, RecordingTransport } = sdk;
12
+ const { createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
13
+
14
+ const outgoingHeaders = createTraceparentHeaders({
15
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
16
+ spanId: "b7ad6b7169203331",
17
+ traceFlags: "01"
18
+ });
19
+ if (outgoingHeaders.traceparent !== "00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01") {
20
+ throw new Error("createTraceparentHeaders produced an unexpected carrier");
21
+ }
13
22
 
14
23
  const client = LogBrewClient.create({
15
24
  apiKey: "LOGBREW_API_KEY",
@@ -5,7 +5,16 @@ const sdk = await import("@logbrew/sdk").catch(async (error) => {
5
5
  throw error;
6
6
  });
7
7
 
8
- const { LogBrewClient, RecordingTransport } = sdk;
8
+ const { createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
9
+
10
+ const outgoingHeaders = createTraceparentHeaders({
11
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
12
+ spanId: "b7ad6b7169203331",
13
+ traceFlags: "01"
14
+ });
15
+ if (outgoingHeaders.traceparent !== "00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01") {
16
+ throw new Error("createTraceparentHeaders produced an unexpected carrier");
17
+ }
9
18
 
10
19
  const client = LogBrewClient.create({
11
20
  apiKey: "LOGBREW_API_KEY",
package/index.cjs CHANGED
@@ -1,7 +1,23 @@
1
- const ISSUE_LEVELS = new Set(["info", "warning", "error", "critical"]);
2
- const LOG_LEVELS = new Set(["debug", "info", "warning", "error"]);
1
+ const SEVERITY_ALIASES = new Map([
2
+ ["trace", "info"],
3
+ ["debug", "info"],
4
+ ["info", "info"],
5
+ ["warn", "warning"],
6
+ ["warning", "warning"],
7
+ ["error", "error"],
8
+ ["fatal", "critical"],
9
+ ["critical", "critical"]
10
+ ]);
11
+ const SEVERITY_VALUES = new Set(SEVERITY_ALIASES.keys());
3
12
  const SPAN_STATUSES = new Set(["ok", "error"]);
4
13
  const ACTION_STATUSES = new Set(["queued", "running", "success", "failure"]);
14
+ const METRIC_KINDS = new Set(["counter", "gauge", "histogram"]);
15
+ const NON_NEGATIVE_METRIC_KINDS = new Set(["counter", "histogram"]);
16
+ const METRIC_TEMPORALITIES_BY_KIND = new Map([
17
+ ["counter", new Set(["delta", "cumulative"])],
18
+ ["gauge", new Set(["instant"])],
19
+ ["histogram", new Set(["delta", "cumulative"])]
20
+ ]);
5
21
  const CONSOLE_METHODS = new Set(["debug", "info", "log", "warn", "error"]);
6
22
  const DEFAULT_CONSOLE_LEVELS = ["debug", "info", "log", "warn", "error"];
7
23
  const PINO_HOST_FIELD = ["host", "name"].join("");
@@ -63,13 +79,17 @@ class RecordingTransport {
63
79
  }
64
80
 
65
81
  class LogBrewClient {
66
- static create({ apiKey, sdkName, sdkVersion, maxRetries = 2 }) {
82
+ static create({ apiKey, sdkName, sdkVersion, maxRetries = 2, eventFilter }) {
67
83
  requireNonEmpty("apiKey", apiKey);
68
84
  requireNonEmpty("sdkName", sdkName);
69
85
  requireNonEmpty("sdkVersion", sdkVersion);
86
+ if (eventFilter !== undefined && typeof eventFilter !== "function") {
87
+ throw new SdkError("validation_error", "eventFilter must be a function");
88
+ }
70
89
 
71
90
  return new LogBrewClient({
72
91
  apiKey,
92
+ eventFilter,
73
93
  sdk: {
74
94
  name: sdkName,
75
95
  language: "javascript",
@@ -79,8 +99,9 @@ class LogBrewClient {
79
99
  });
80
100
  }
81
101
 
82
- constructor({ apiKey, sdk, maxRetries }) {
102
+ constructor({ apiKey, sdk, maxRetries, eventFilter }) {
83
103
  this.apiKey = apiKey;
104
+ this.eventFilter = eventFilter;
84
105
  this.sdk = sdk;
85
106
  this.maxRetries = maxRetries;
86
107
  this.events = [];
@@ -119,6 +140,10 @@ class LogBrewClient {
119
140
  this.#pushEvent("action", id, timestamp, validateAction(attributes));
120
141
  }
121
142
 
143
+ metric(id, timestamp, attributes) {
144
+ this.#pushEvent("metric", id, timestamp, validateMetric(attributes));
145
+ }
146
+
122
147
  async flush(transport) {
123
148
  if (this.closed) {
124
149
  throw new SdkError("shutdown_error", "client is already shut down");
@@ -141,7 +166,11 @@ class LogBrewClient {
141
166
  }
142
167
  requireNonEmpty("event id", id);
143
168
  requireTimestamp(timestamp);
144
- this.events.push({ type: eventType, id, timestamp, attributes });
169
+ const event = { type: eventType, id, timestamp, attributes };
170
+ if (this.eventFilter && this.eventFilter(cloneEvent(event)) === false) {
171
+ return;
172
+ }
173
+ this.events.push(event);
145
174
  }
146
175
 
147
176
  async #flushInternal(transport) {
@@ -326,7 +355,7 @@ function logAttributesFromConsoleArgs(method, args, options = {}) {
326
355
  function logbrewLevelFromConsoleMethod(method) {
327
356
  switch (method) {
328
357
  case "debug":
329
- return "debug";
358
+ return "info";
330
359
  case "warn":
331
360
  return "warning";
332
361
  case "error":
@@ -339,6 +368,44 @@ function logbrewLevelFromConsoleMethod(method) {
339
368
  }
340
369
  }
341
370
 
371
+ function createProductActionAttributes(action, options = {}) {
372
+ const details = productActionDetails(action);
373
+ return {
374
+ name: details.name,
375
+ status: details.status,
376
+ metadata: compactMetadata({
377
+ source: "product.action",
378
+ ...compactMetadata(options.metadata),
379
+ ...compactMetadata(details.metadata),
380
+ routeTemplate: sanitizeRouteTemplate(details.routeTemplate),
381
+ sessionId: stringOrUndefined(details.sessionId),
382
+ traceId: stringOrUndefined(details.traceId),
383
+ screen: stringOrUndefined(details.screen),
384
+ funnel: stringOrUndefined(details.funnel),
385
+ step: stringOrUndefined(details.step)
386
+ })
387
+ };
388
+ }
389
+
390
+ function createNetworkMilestoneAttributes(request, options = {}) {
391
+ const details = networkMilestoneDetails(request);
392
+ return {
393
+ name: details.name,
394
+ status: details.status,
395
+ metadata: compactMetadata({
396
+ source: "network.milestone",
397
+ ...compactMetadata(options.metadata),
398
+ ...compactMetadata(details.metadata),
399
+ routeTemplate: details.routeTemplate,
400
+ method: details.method,
401
+ statusCode: details.statusCode,
402
+ durationMs: details.durationMs,
403
+ sessionId: stringOrUndefined(details.sessionId),
404
+ traceId: stringOrUndefined(details.traceId)
405
+ })
406
+ };
407
+ }
408
+
342
409
  function parseTraceparent(traceparent) {
343
410
  if (typeof traceparent !== "string" || traceparent.trim() === "") {
344
411
  throw new SdkError("validation_error", "traceparent must be non-empty");
@@ -379,6 +446,10 @@ function createTraceparent({ traceId, spanId, traceFlags = "01" }) {
379
446
  return `00-${traceId.toLowerCase()}-${spanId.toLowerCase()}-${traceFlags.toLowerCase()}`;
380
447
  }
381
448
 
449
+ function createTraceparentHeaders(input) {
450
+ return { traceparent: createTraceparent(input) };
451
+ }
452
+
382
453
  function spanAttributesFromTraceparent(traceparent, attributes) {
383
454
  if (!attributes || Array.isArray(attributes) || typeof attributes !== "object") {
384
455
  throw new SdkError("validation_error", "span attributes must be an object");
@@ -646,15 +717,16 @@ function logbrewLevelFromWinstonLevel(level) {
646
717
  switch (String(level).toLowerCase()) {
647
718
  case "debug":
648
719
  case "silly":
649
- return "debug";
720
+ return "info";
650
721
  case "warn":
651
722
  case "warning":
652
723
  return "warning";
653
724
  case "error":
725
+ return "error";
654
726
  case "crit":
655
727
  case "critical":
656
728
  case "fatal":
657
- return "error";
729
+ return "critical";
658
730
  case "http":
659
731
  case "verbose":
660
732
  case "info":
@@ -757,6 +829,9 @@ function timestampFromPinoRecord(record, fallbackTimestamp) {
757
829
 
758
830
  function logbrewLevelFromPinoLevel(level) {
759
831
  if (typeof level === "number" && Number.isFinite(level)) {
832
+ if (level >= 60) {
833
+ return "critical";
834
+ }
760
835
  if (level >= 50) {
761
836
  return "error";
762
837
  }
@@ -766,19 +841,21 @@ function logbrewLevelFromPinoLevel(level) {
766
841
  if (level >= 30) {
767
842
  return "info";
768
843
  }
769
- return "debug";
844
+ return "info";
770
845
  }
771
846
 
772
847
  switch (String(level).toLowerCase()) {
773
848
  case "trace":
774
849
  case "debug":
775
- return "debug";
850
+ return "info";
776
851
  case "warn":
777
852
  case "warning":
778
853
  return "warning";
779
854
  case "error":
780
- case "fatal":
781
855
  return "error";
856
+ case "fatal":
857
+ case "critical":
858
+ return "critical";
782
859
  case "info":
783
860
  default:
784
861
  return "info";
@@ -881,6 +958,12 @@ function requireAllowedValue(label, value, allowedValues) {
881
958
  }
882
959
  }
883
960
 
961
+ function requireFiniteNumber(label, value) {
962
+ if (typeof value !== "number" || !Number.isFinite(value)) {
963
+ throw new SdkError("validation_error", `${label} must be a finite number`);
964
+ }
965
+ }
966
+
884
967
  function requireTraceId(traceId) {
885
968
  if (typeof traceId !== "string" || !/^[0-9a-fA-F]{32}$/u.test(traceId)) {
886
969
  throw new SdkError("validation_error", "traceId must be 32 lowercase or uppercase hex characters");
@@ -930,6 +1013,13 @@ function cloneMetadata(metadata) {
930
1013
  return { ...metadata };
931
1014
  }
932
1015
 
1016
+ function cloneEvent(event) {
1017
+ const attributes = event.attributes.metadata === undefined
1018
+ ? { ...event.attributes }
1019
+ : { ...event.attributes, metadata: { ...event.attributes.metadata } };
1020
+ return { ...event, attributes };
1021
+ }
1022
+
933
1023
  function validateRelease(attributes) {
934
1024
  requireNonEmpty("release version", attributes.version);
935
1025
  if (attributes.commit !== undefined) {
@@ -952,24 +1042,29 @@ function validateEnvironment(attributes) {
952
1042
 
953
1043
  function validateIssue(attributes) {
954
1044
  requireNonEmpty("issue title", attributes.title);
955
- requireAllowedValue("issue level", attributes.level, ISSUE_LEVELS);
1045
+ const level = normalizeSeverity("issue level", attributes.level);
956
1046
  return withMetadata({
957
1047
  title: attributes.title,
958
- level: attributes.level,
1048
+ level,
959
1049
  ...(attributes.message !== undefined ? { message: attributes.message } : {})
960
1050
  }, attributes.metadata);
961
1051
  }
962
1052
 
963
1053
  function validateLog(attributes) {
964
1054
  requireNonEmpty("log message", attributes.message);
965
- requireAllowedValue("log level", attributes.level, LOG_LEVELS);
1055
+ const level = normalizeSeverity("log level", attributes.level);
966
1056
  return withMetadata({
967
1057
  message: attributes.message,
968
- level: attributes.level,
1058
+ level,
969
1059
  ...(attributes.logger !== undefined ? { logger: attributes.logger } : {})
970
1060
  }, attributes.metadata);
971
1061
  }
972
1062
 
1063
+ function normalizeSeverity(label, value) {
1064
+ requireAllowedValue(label, value, SEVERITY_VALUES);
1065
+ return SEVERITY_ALIASES.get(value);
1066
+ }
1067
+
973
1068
  function validateSpan(attributes) {
974
1069
  requireNonEmpty("span name", attributes.name);
975
1070
  requireNonEmpty("span traceId", attributes.traceId);
@@ -1002,6 +1097,146 @@ function validateAction(attributes) {
1002
1097
  }, attributes.metadata);
1003
1098
  }
1004
1099
 
1100
+ function validateMetric(attributes) {
1101
+ requireNonEmpty("metric name", attributes.name);
1102
+ requireAllowedValue("metric kind", attributes.kind, METRIC_KINDS);
1103
+ requireFiniteNumber("metric value", attributes.value);
1104
+ requireNonEmpty("metric unit", attributes.unit);
1105
+
1106
+ const allowedTemporalities = METRIC_TEMPORALITIES_BY_KIND.get(attributes.kind);
1107
+ requireAllowedValue(`metric temporality for ${attributes.kind}`, attributes.temporality, allowedTemporalities);
1108
+ if (NON_NEGATIVE_METRIC_KINDS.has(attributes.kind) && attributes.value < 0) {
1109
+ throw new SdkError("validation_error", `metric ${attributes.kind} value must be non-negative`);
1110
+ }
1111
+
1112
+ return withMetadata({
1113
+ name: attributes.name,
1114
+ kind: attributes.kind,
1115
+ value: attributes.value,
1116
+ unit: attributes.unit,
1117
+ temporality: attributes.temporality
1118
+ }, attributes.metadata);
1119
+ }
1120
+
1121
+ function productActionDetails(action) {
1122
+ if (typeof action === "string") {
1123
+ return { name: action, status: "success" };
1124
+ }
1125
+ if (!action || Array.isArray(action) || typeof action !== "object") {
1126
+ throw new SdkError("validation_error", "product action must be a string or object");
1127
+ }
1128
+ requireNonEmpty("product action name", action.name);
1129
+ const status = action.status === undefined ? "success" : action.status;
1130
+ requireAllowedValue("product action status", status, ACTION_STATUSES);
1131
+ return {
1132
+ funnel: action.funnel,
1133
+ metadata: action.metadata,
1134
+ name: action.name,
1135
+ routeTemplate: action.routeTemplate,
1136
+ screen: action.screen,
1137
+ sessionId: action.sessionId,
1138
+ status,
1139
+ step: action.step,
1140
+ traceId: action.traceId
1141
+ };
1142
+ }
1143
+
1144
+ function networkMilestoneDetails(request) {
1145
+ if (typeof request === "string") {
1146
+ return networkMilestoneDetails({ routeTemplate: request });
1147
+ }
1148
+ if (!request || Array.isArray(request) || typeof request !== "object") {
1149
+ throw new SdkError("validation_error", "network milestone must be a string or object");
1150
+ }
1151
+
1152
+ const routeTemplate = sanitizeRouteTemplate(request.routeTemplate);
1153
+ requireNonEmpty("network milestone routeTemplate", routeTemplate);
1154
+ const method = normalizeHttpMethod(request.method);
1155
+ const statusCode = statusCodeOrUndefined(request.statusCode);
1156
+ const status = request.status === undefined
1157
+ ? statusFromStatusCode(statusCode)
1158
+ : request.status;
1159
+ requireAllowedValue("network milestone status", status, ACTION_STATUSES);
1160
+ const durationMs = nonNegativeNumberOrUndefined("network milestone durationMs", request.durationMs);
1161
+ const name = typeof request.name === "string" && request.name.trim() !== ""
1162
+ ? request.name
1163
+ : `network.${method.toLowerCase()} ${routeTemplate}`;
1164
+
1165
+ return {
1166
+ durationMs,
1167
+ metadata: request.metadata,
1168
+ method,
1169
+ name,
1170
+ routeTemplate,
1171
+ sessionId: request.sessionId,
1172
+ status,
1173
+ statusCode,
1174
+ traceId: request.traceId
1175
+ };
1176
+ }
1177
+
1178
+ function sanitizeRouteTemplate(routeTemplate) {
1179
+ if (routeTemplate === undefined) {
1180
+ return undefined;
1181
+ }
1182
+ if (typeof routeTemplate !== "string") {
1183
+ throw new SdkError("validation_error", "routeTemplate must be a string");
1184
+ }
1185
+ const trimmed = routeTemplate.trim();
1186
+ if (trimmed === "") {
1187
+ return "";
1188
+ }
1189
+ try {
1190
+ const url = new URL(trimmed, "https://logbrew.example");
1191
+ return url.pathname || "/";
1192
+ } catch {
1193
+ return trimmed.split(/[?#]/u)[0] || "/";
1194
+ }
1195
+ }
1196
+
1197
+ function normalizeHttpMethod(method) {
1198
+ const value = method === undefined ? "GET" : method;
1199
+ if (typeof value !== "string" || value.trim() === "") {
1200
+ throw new SdkError("validation_error", "network milestone method must be a non-empty string");
1201
+ }
1202
+ const normalized = value.trim().toUpperCase();
1203
+ if (!/^[A-Z][A-Z0-9_-]*$/u.test(normalized)) {
1204
+ throw new SdkError("validation_error", "network milestone method must be a valid HTTP method");
1205
+ }
1206
+ return normalized;
1207
+ }
1208
+
1209
+ function statusCodeOrUndefined(value) {
1210
+ if (value === undefined) {
1211
+ return undefined;
1212
+ }
1213
+ if (!Number.isInteger(value) || value < 100 || value > 599) {
1214
+ throw new SdkError("validation_error", "network milestone statusCode must be an integer from 100 to 599");
1215
+ }
1216
+ return value;
1217
+ }
1218
+
1219
+ function statusFromStatusCode(statusCode) {
1220
+ if (statusCode !== undefined && statusCode >= 400) {
1221
+ return "failure";
1222
+ }
1223
+ return "success";
1224
+ }
1225
+
1226
+ function nonNegativeNumberOrUndefined(label, value) {
1227
+ if (value === undefined) {
1228
+ return undefined;
1229
+ }
1230
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1231
+ throw new SdkError("validation_error", `${label} must be a non-negative number`);
1232
+ }
1233
+ return value;
1234
+ }
1235
+
1236
+ function stringOrUndefined(value) {
1237
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
1238
+ }
1239
+
1005
1240
  function withMetadata(attributes, metadata) {
1006
1241
  const safeMetadata = cloneMetadata(metadata);
1007
1242
  return safeMetadata === undefined
@@ -1082,7 +1317,10 @@ function formatConsoleArgument(value, includeErrorStack) {
1082
1317
  }
1083
1318
 
1084
1319
  module.exports = {
1320
+ createNetworkMilestoneAttributes,
1321
+ createProductActionAttributes,
1085
1322
  createTraceparent,
1323
+ createTraceparentHeaders,
1086
1324
  createLogBrewPinoDestination,
1087
1325
  createLogBrewWinstonTransport,
1088
1326
  installLogBrewConsoleCapture,
package/index.d.cts CHANGED
@@ -2,6 +2,12 @@
2
2
  export type MetadataValue = string | number | boolean | null;
3
3
  /** Structured metadata map shared by public LogBrew event attribute types. */
4
4
  export type Metadata = Record<string, MetadataValue>;
5
+ /** Canonical user-facing severity categories accepted by LogBrew. */
6
+ export type Severity = "info" | "warning" | "error" | "critical";
7
+ /** Runtime-level aliases accepted for compatibility and normalized before send. */
8
+ export type SeverityAlias = "trace" | "debug" | "warn" | "fatal";
9
+ /** Public severity input accepted by issue and log attributes. */
10
+ export type SeverityInput = Severity | SeverityAlias;
5
11
 
6
12
  /** Parsed W3C trace context from a traceparent value. */
7
13
  export type TraceparentContext = {
@@ -46,7 +52,7 @@ export type EnvironmentAttributes = {
46
52
  /** Public issue event attributes. */
47
53
  export type IssueAttributes = {
48
54
  title: string;
49
- level: "info" | "warning" | "error" | "critical";
55
+ level: SeverityInput;
50
56
  message?: string;
51
57
  metadata?: Metadata;
52
58
  };
@@ -54,7 +60,7 @@ export type IssueAttributes = {
54
60
  /** Public log event attributes. */
55
61
  export type LogAttributes = {
56
62
  message: string;
57
- level: "debug" | "info" | "warning" | "error";
63
+ level: SeverityInput;
58
64
  logger?: string;
59
65
  metadata?: Metadata;
60
66
  };
@@ -177,6 +183,54 @@ export type ActionAttributes = {
177
183
  metadata?: Metadata;
178
184
  };
179
185
 
186
+ /** App-owned product step input for agent-readable action timelines. */
187
+ export type ProductActionInput = string | {
188
+ name: string;
189
+ status?: ActionAttributes["status"];
190
+ sessionId?: string;
191
+ traceId?: string;
192
+ routeTemplate?: string;
193
+ screen?: string;
194
+ funnel?: string;
195
+ step?: string;
196
+ metadata?: Metadata;
197
+ };
198
+
199
+ /** App-owned API milestone input for agent-readable network timelines. */
200
+ export type NetworkMilestoneInput = string | {
201
+ name?: string;
202
+ routeTemplate: string;
203
+ method?: string;
204
+ status?: ActionAttributes["status"];
205
+ statusCode?: number;
206
+ durationMs?: number;
207
+ sessionId?: string;
208
+ traceId?: string;
209
+ metadata?: Metadata;
210
+ };
211
+
212
+ /** Shared timeline helper options for primitive app metadata. */
213
+ export type TimelineAttributesOptions = {
214
+ metadata?: Metadata;
215
+ };
216
+
217
+ /** Public metric event attributes. Use low-cardinality metadata only. */
218
+ export type MetricAttributes = {
219
+ name: string;
220
+ kind: "counter" | "histogram";
221
+ value: number;
222
+ unit: string;
223
+ temporality: "delta" | "cumulative";
224
+ metadata?: Metadata;
225
+ } | {
226
+ name: string;
227
+ kind: "gauge";
228
+ value: number;
229
+ unit: string;
230
+ temporality: "instant";
231
+ metadata?: Metadata;
232
+ };
233
+
180
234
  /** Public event union used in preview and transport payloads. */
181
235
  export type Event =
182
236
  | { type: "release"; id: string; timestamp: string; attributes: ReleaseAttributes }
@@ -184,7 +238,11 @@ export type Event =
184
238
  | { type: "issue"; id: string; timestamp: string; attributes: IssueAttributes }
185
239
  | { type: "log"; id: string; timestamp: string; attributes: LogAttributes }
186
240
  | { type: "span"; id: string; timestamp: string; attributes: SpanAttributes }
187
- | { type: "action"; id: string; timestamp: string; attributes: ActionAttributes };
241
+ | { type: "action"; id: string; timestamp: string; attributes: ActionAttributes }
242
+ | { type: "metric"; id: string; timestamp: string; attributes: MetricAttributes };
243
+
244
+ /** Drop-only event filter called after validation and before an event is queued. */
245
+ export type EventFilter = (event: Event) => boolean | void;
188
246
 
189
247
  /** Stable transport response returned from flush and shutdown operations. */
190
248
  export type TransportResponse = {
@@ -234,6 +292,7 @@ export declare class LogBrewClient {
234
292
  sdkName: string;
235
293
  sdkVersion: string;
236
294
  maxRetries?: number;
295
+ eventFilter?: EventFilter;
237
296
  }): LogBrewClient;
238
297
  /** Return the queued event count currently buffered in memory. */
239
298
  pendingEvents(): number;
@@ -245,6 +304,7 @@ export declare class LogBrewClient {
245
304
  log(id: string, timestamp: string, attributes: LogAttributes): void;
246
305
  span(id: string, timestamp: string, attributes: SpanAttributes): void;
247
306
  action(id: string, timestamp: string, attributes: ActionAttributes): void;
307
+ metric(id: string, timestamp: string, attributes: MetricAttributes): void;
248
308
  /** Flush queued events through a transport while preserving retry semantics. */
249
309
  flush(transport: Transport): Promise<TransportResponse>;
250
310
  /** Flush queued events, then mark the client closed so later writes fail. */
@@ -254,6 +314,18 @@ export declare class LogBrewClient {
254
314
  /** Install explicit console capture while preserving the target console's normal output behavior. */
255
315
  export declare function installLogBrewConsoleCapture(config: ConsoleCaptureConfig): ConsoleCaptureHandle;
256
316
 
317
+ /** Create safe action attributes for an app-owned product step without automatic UI capture. */
318
+ export declare function createProductActionAttributes(
319
+ action: ProductActionInput,
320
+ options?: TimelineAttributesOptions
321
+ ): ActionAttributes;
322
+
323
+ /** Create safe action attributes for an app-owned network milestone without HTTP client patching. */
324
+ export declare function createNetworkMilestoneAttributes(
325
+ request: NetworkMilestoneInput,
326
+ options?: TimelineAttributesOptions
327
+ ): ActionAttributes;
328
+
257
329
  /** Convert console arguments into safe LogBrew log attributes without installing capture. */
258
330
  export declare function logAttributesFromConsoleArgs(
259
331
  method: ConsoleMethodName,
@@ -274,6 +346,9 @@ export declare function parseTraceparent(traceparent: string): TraceparentContex
274
346
  /** Create a W3C traceparent value from explicit trace/span ids. */
275
347
  export declare function createTraceparent(input: TraceparentInput): string;
276
348
 
349
+ /** Create an explicit outbound header carrier containing only traceparent. */
350
+ export declare function createTraceparentHeaders(input: TraceparentInput): { traceparent: string };
351
+
277
352
  /** Build LogBrew span attributes that continue an incoming W3C traceparent value. */
278
353
  export declare function spanAttributesFromTraceparent(
279
354
  traceparent: string,
package/index.d.ts CHANGED
@@ -2,6 +2,12 @@
2
2
  export type MetadataValue = string | number | boolean | null;
3
3
  /** Structured metadata map shared by public LogBrew event attribute types. */
4
4
  export type Metadata = Record<string, MetadataValue>;
5
+ /** Canonical user-facing severity categories accepted by LogBrew. */
6
+ export type Severity = "info" | "warning" | "error" | "critical";
7
+ /** Runtime-level aliases accepted for compatibility and normalized before send. */
8
+ export type SeverityAlias = "trace" | "debug" | "warn" | "fatal";
9
+ /** Public severity input accepted by issue and log attributes. */
10
+ export type SeverityInput = Severity | SeverityAlias;
5
11
 
6
12
  /** Parsed W3C trace context from a traceparent value. */
7
13
  export type TraceparentContext = {
@@ -46,7 +52,7 @@ export type EnvironmentAttributes = {
46
52
  /** Public issue event attributes. */
47
53
  export type IssueAttributes = {
48
54
  title: string;
49
- level: "info" | "warning" | "error" | "critical";
55
+ level: SeverityInput;
50
56
  message?: string;
51
57
  metadata?: Metadata;
52
58
  };
@@ -54,7 +60,7 @@ export type IssueAttributes = {
54
60
  /** Public log event attributes. */
55
61
  export type LogAttributes = {
56
62
  message: string;
57
- level: "debug" | "info" | "warning" | "error";
63
+ level: SeverityInput;
58
64
  logger?: string;
59
65
  metadata?: Metadata;
60
66
  };
@@ -177,6 +183,54 @@ export type ActionAttributes = {
177
183
  metadata?: Metadata;
178
184
  };
179
185
 
186
+ /** App-owned product step input for agent-readable action timelines. */
187
+ export type ProductActionInput = string | {
188
+ name: string;
189
+ status?: ActionAttributes["status"];
190
+ sessionId?: string;
191
+ traceId?: string;
192
+ routeTemplate?: string;
193
+ screen?: string;
194
+ funnel?: string;
195
+ step?: string;
196
+ metadata?: Metadata;
197
+ };
198
+
199
+ /** App-owned API milestone input for agent-readable network timelines. */
200
+ export type NetworkMilestoneInput = string | {
201
+ name?: string;
202
+ routeTemplate: string;
203
+ method?: string;
204
+ status?: ActionAttributes["status"];
205
+ statusCode?: number;
206
+ durationMs?: number;
207
+ sessionId?: string;
208
+ traceId?: string;
209
+ metadata?: Metadata;
210
+ };
211
+
212
+ /** Shared timeline helper options for primitive app metadata. */
213
+ export type TimelineAttributesOptions = {
214
+ metadata?: Metadata;
215
+ };
216
+
217
+ /** Public metric event attributes. Use low-cardinality metadata only. */
218
+ export type MetricAttributes = {
219
+ name: string;
220
+ kind: "counter" | "histogram";
221
+ value: number;
222
+ unit: string;
223
+ temporality: "delta" | "cumulative";
224
+ metadata?: Metadata;
225
+ } | {
226
+ name: string;
227
+ kind: "gauge";
228
+ value: number;
229
+ unit: string;
230
+ temporality: "instant";
231
+ metadata?: Metadata;
232
+ };
233
+
180
234
  /** Public event union used in preview and transport payloads. */
181
235
  export type Event =
182
236
  | { type: "release"; id: string; timestamp: string; attributes: ReleaseAttributes }
@@ -184,7 +238,11 @@ export type Event =
184
238
  | { type: "issue"; id: string; timestamp: string; attributes: IssueAttributes }
185
239
  | { type: "log"; id: string; timestamp: string; attributes: LogAttributes }
186
240
  | { type: "span"; id: string; timestamp: string; attributes: SpanAttributes }
187
- | { type: "action"; id: string; timestamp: string; attributes: ActionAttributes };
241
+ | { type: "action"; id: string; timestamp: string; attributes: ActionAttributes }
242
+ | { type: "metric"; id: string; timestamp: string; attributes: MetricAttributes };
243
+
244
+ /** Drop-only event filter called after validation and before an event is queued. */
245
+ export type EventFilter = (event: Event) => boolean | void;
188
246
 
189
247
  /** Stable transport response returned from flush and shutdown operations. */
190
248
  export type TransportResponse = {
@@ -234,6 +292,7 @@ export declare class LogBrewClient {
234
292
  sdkName: string;
235
293
  sdkVersion: string;
236
294
  maxRetries?: number;
295
+ eventFilter?: EventFilter;
237
296
  }): LogBrewClient;
238
297
  /** Return the queued event count currently buffered in memory. */
239
298
  pendingEvents(): number;
@@ -245,6 +304,7 @@ export declare class LogBrewClient {
245
304
  log(id: string, timestamp: string, attributes: LogAttributes): void;
246
305
  span(id: string, timestamp: string, attributes: SpanAttributes): void;
247
306
  action(id: string, timestamp: string, attributes: ActionAttributes): void;
307
+ metric(id: string, timestamp: string, attributes: MetricAttributes): void;
248
308
  /** Flush queued events through a transport while preserving retry semantics. */
249
309
  flush(transport: Transport): Promise<TransportResponse>;
250
310
  /** Flush queued events, then mark the client closed so later writes fail. */
@@ -254,6 +314,18 @@ export declare class LogBrewClient {
254
314
  /** Install explicit console capture while preserving the target console's normal output behavior. */
255
315
  export declare function installLogBrewConsoleCapture(config: ConsoleCaptureConfig): ConsoleCaptureHandle;
256
316
 
317
+ /** Create safe action attributes for an app-owned product step without automatic UI capture. */
318
+ export declare function createProductActionAttributes(
319
+ action: ProductActionInput,
320
+ options?: TimelineAttributesOptions
321
+ ): ActionAttributes;
322
+
323
+ /** Create safe action attributes for an app-owned network milestone without HTTP client patching. */
324
+ export declare function createNetworkMilestoneAttributes(
325
+ request: NetworkMilestoneInput,
326
+ options?: TimelineAttributesOptions
327
+ ): ActionAttributes;
328
+
257
329
  /** Convert console arguments into safe LogBrew log attributes without installing capture. */
258
330
  export declare function logAttributesFromConsoleArgs(
259
331
  method: ConsoleMethodName,
@@ -274,6 +346,9 @@ export declare function parseTraceparent(traceparent: string): TraceparentContex
274
346
  /** Create a W3C traceparent value from explicit trace/span ids. */
275
347
  export declare function createTraceparent(input: TraceparentInput): string;
276
348
 
349
+ /** Create an explicit outbound header carrier containing only traceparent. */
350
+ export declare function createTraceparentHeaders(input: TraceparentInput): { traceparent: string };
351
+
277
352
  /** Build LogBrew span attributes that continue an incoming W3C traceparent value. */
278
353
  export declare function spanAttributesFromTraceparent(
279
354
  traceparent: string,
package/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import sdk from "./index.cjs";
2
2
 
3
3
  export const {
4
+ createNetworkMilestoneAttributes,
5
+ createProductActionAttributes,
4
6
  createTraceparent,
7
+ createTraceparentHeaders,
5
8
  createLogBrewPinoDestination,
6
9
  createLogBrewWinstonTransport,
7
10
  installLogBrewConsoleCapture,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",