@logbrew/sdk 0.1.35 → 0.1.37

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
@@ -100,9 +100,9 @@ npx logbrew-release-artifacts upload-js \
100
100
  --allow-hosted
101
101
  ```
102
102
 
103
- Non-loopback endpoints require `--allow-hosted`, a UUID `projectId` created by `manifest-js --project-id`, HTTPS, and no embedded auth values, query strings, or fragments. Local loopback preparation remains valid without a project ID. The upload command never uses normal SDK ingest keys or account/session API auth values. Full backend-symbolicated issue support is separate from artifact upload until your project has completed hosted symbolication for its release.
103
+ Non-loopback endpoints require `--allow-hosted`, a UUID `projectId` created by `manifest-js --project-id`, HTTPS, and no embedded auth values, query strings, or fragments. Local loopback preparation remains valid without a project ID. The upload command never uses normal SDK ingest keys or account/session API auth values. Issues show symbolicated frames after LogBrew processes the matching release artifacts.
104
104
 
105
- When you capture a JavaScript error, use `createIssueAttributesFromError()` to keep error metadata structured and source-map-friendly without sending raw stack text by default. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../docs/exception-chain-evidence.md). A bundle prepared by the Vite plugin registers its Debug ID automatically; use `debugIdMap` only for a different app-owned build flow:
105
+ When you capture a JavaScript error, use `createIssueAttributesFromError()` to keep error metadata structured and source-map-friendly without sending raw stack text by default. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../README.md#exception-chains). A bundle prepared by the Vite plugin registers its Debug ID automatically; use `debugIdMap` only for a different app-owned build flow:
106
106
 
107
107
  ```js
108
108
  import { createIssueAttributesFromError, LogBrewClient } from "@logbrew/sdk";
@@ -452,7 +452,7 @@ These examples use `@opentelemetry/sdk-trace` 3. With `@opentelemetry/sdk-trace-
452
452
 
453
453
  The OTel processor and exporter follow normal OTel sampled-span behavior by default and summarize up to eight span events and eight links. Set `includeTraceSummary: true` when you also want one synthetic `opentelemetry.trace:<root-name>` span per trace on processor `forceFlush()`/`shutdown()` or exporter `export()`/`forceFlush()`/`shutdown()`; the summary carries the trace ID, span count, error count, root span ID/name/kind, duration, and safe service/environment/route metadata so a batch reads like one request or transaction. They copy only a small safe default set such as service, environment, route, method, status code, span kind, instrumentation scope, exception type, exception event count, escaped exception count, and dropped-count metadata. Exception counts scan the readable span's events, while emitted exception type lists stay bounded. Escaped exception events on unset-status spans mark the LogBrew span as `error`; explicit OTel `OK` status stays `ok`. Additional event/link/span/resource attributes require explicit allowlists, and high-risk keys such as full URLs, headers, query strings, payloads, cookies, private auth values, DB statements, exception messages, and stacks stay blocked. Concurrent flush calls use one active send and one coalesced trailing drain: later spans leave the queue before their caller completes, while a failed active send consumes no extra retry budget. These helpers do not add an OpenTelemetry dependency, patch clients, own tracer providers, serialize baggage or tracestate, copy raw propagation headers, or capture request/response bodies.
454
454
 
455
- 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).
455
+ 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](../../README.md#what-you-can-capture).
456
456
 
457
457
  ## Event Filtering
458
458
 
@@ -499,7 +499,7 @@ console.log(JSON.stringify(health));
499
499
  await client.shutdown();
500
500
  ```
501
501
 
502
- Set `automaticDelivery: false` for explicit manual-only operation; the owned transport still lets you call `flush()` and `shutdown()` without passing it again. Lower-level clients without an owned transport remain manual and continue to accept `flush(transport)` and `shutdown(transport)`. Retryable automatic failures use equal-jitter exponential backoff capped at 60 seconds. Authentication, rate-limit, and other non-retryable failures pause automatic sends while retaining the exact failed batch; an explicit successful `flush()` with the owned transport clears the pause.
502
+ Set `automaticDelivery: false` for explicit manual-only operation; the owned transport still lets you call `flush()` and `shutdown()` without passing it again. Lower-level clients without an owned transport remain manual and continue to accept `flush(transport)` and `shutdown(transport)`. Retryable automatic failures use equal-jitter exponential backoff capped at 60 seconds. A `429` that carries a `Retry-After` delay or `code: "rate_limited"` waits that long, from 1 second to 1 hour plus up to 10 percent jitter, then resends the same batch on its own. Authentication failures, a `429` with `code: "usage_limit_exceeded"` or with neither signal, and other non-retryable failures pause automatic sends while retaining the exact failed batch; an explicit successful `flush()` with the owned transport clears the pause.
503
503
 
504
504
  `deliveryHealth()` returns a frozen, JSON-serializable schema. It reports lifecycle and delivery states, memory or persistent storage, current and startup-hydrated queue counts/bytes, in-flight and coalesced work, client-lifetime accepted totals, fixed drop reasons, bounded retry state, transport status classes, and monotonic-within-client transition timestamps. Counters saturate instead of overflowing. The snapshot never includes event content or IDs, messages or attributes, authentication material, transport destinations or headers, filesystem paths, arbitrary metadata, numeric HTTP status, response text, or raw errors.
505
505
 
@@ -536,11 +536,11 @@ Flush requests are also bounded. Core and Node default to 100 events and 256 KiB
536
536
 
537
537
  `flush()` calls are serialized so concurrent callers cannot send the same queue prefix. Each call owns the events present when its turn starts. Events captured while its transport is awaiting remain queued for the next flush. A 2xx response removes only that acknowledged prefix; if a later batch fails, the failed batch and every later event remain in their original order. Retries reuse the exact same request body.
538
538
 
539
- A `401` transport response raises `SdkError` with code `unauthenticated`; a `429` response raises code `rate_limited` and includes `retryAfterMs` when the transport exposes a `Retry-After` delay. Those outcomes pause automatic delivery because authentication and account usage are app/service-owned recovery states, not SDK retry loops. HTTP `408`, `5xx`, and retryable `TransportError` exhaustion retain the failed bytes and schedule bounded equal-jitter backoff. `maxRetries` is the number of immediate retries after the first attempt and must be a non-negative integer. `shutdown()` cancels automatic scheduling, rejects new capture while it drains once, closes only after success, and reopens the intact unsent remainder after failure. LogBrew does not derive account usage locally, sleep inside transport retries, or drop queued events on rate limits.
539
+ A `401` transport response raises `SdkError` with code `unauthenticated`; a `429` response raises code `rate_limited` and includes `retryAfterMs` when the transport exposes a `Retry-After` delay. Transports can also return `errorCode` from the `429` body. A `429` with `retryAfterMs` or `errorCode: "rate_limited"` raises a retryable error and automatic delivery waits it out; `errorCode: "usage_limit_exceeded"` or a `429` with neither signal pauses automatic delivery, like a `401`. HTTP `408`, `5xx`, and retryable `TransportError` exhaustion retain the failed bytes and schedule bounded equal-jitter backoff. `maxRetries` is the number of immediate retries after the first attempt and must be a non-negative integer. `shutdown()` cancels automatic scheduling, rejects new capture while it drains once, closes only after success, and reopens the intact unsent remainder after failure. LogBrew does not derive account usage locally, sleep inside transport retries, or drop queued events on rate limits.
540
540
 
541
541
  ## Support Ticket Drafts
542
542
 
543
- Use `createSupportTicketDraft()` when a developer or support agent needs a local JSON payload for the planned LogBrew support-ticket API. The helper validates the public source/category contract, converts JavaScript camelCase inputs to the planned backend create payload fields, and redacts token-like diagnostics before returning the object.
543
+ Use `createSupportTicketDraft()` to build the JSON body for a LogBrew support ticket. The helper validates the source and category, converts JavaScript camelCase inputs to the ticket fields, and redacts token-like diagnostics before returning the object.
544
544
 
545
545
  ```js
546
546
  import { createSupportTicketDraft } from "@logbrew/sdk";
@@ -570,7 +570,7 @@ const draft = createSupportTicketDraft({
570
570
  console.log(JSON.stringify(draft, null, 2));
571
571
  ```
572
572
 
573
- This helper does not send data, open support tickets, call `POST /api/support/tickets`, use account/session API credentials, or infer backend usage/quota state. Support routes are backend-owned and should only be called by an explicit user or agent action after backend reports deployed support-ticket routes. Diagnostics are bounded to JSON-like values; auth values, cookies, tokens, local paths, URL origins, hidden payloads, and unsupported objects are redacted or omitted.
573
+ The helper sends nothing. To open the ticket, send it to `POST /api/support/tickets` or run `logbrew support create`. See [Report a problem](https://docs.logbrew.co/guides/report-a-problem). Diagnostics are bounded to JSON-like values; auth values, cookies, tokens, local paths, URL origins, hidden payloads, and unsupported objects are redacted or omitted.
574
574
 
575
575
  ## Agent-Readable Timelines
576
576
 
@@ -613,7 +613,7 @@ client.action("evt_payment_api", new Date().toISOString(), createNetworkMileston
613
613
 
614
614
  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. Product actions also carry the reserved version-1 `interaction` analytics classification; network milestones do not. Keep metadata low-cardinality, such as `sessionId`, `traceId`, `routeTemplate`, `method`, `statusCode`, `durationMs`, `screen`, `funnel`, and `step`.
615
615
 
616
- `PRODUCT_ANALYTICS_SCHEMA_VERSION` and `PRODUCT_ANALYTICS_KINDS` expose the classification values supported by the installed package. The reserved analytics fields cannot be replaced through caller metadata. See the repository [product analytics capture contract](../../docs/product-analytics-contract.md) before constructing those fields directly.
616
+ `PRODUCT_ANALYTICS_SCHEMA_VERSION` and `PRODUCT_ANALYTICS_KINDS` expose the classification values supported by the installed package. The reserved analytics fields cannot be replaced through caller metadata. See the repository [product analytics capture contract](../../README.md#product-analytics-capture) before constructing those fields directly.
617
617
 
618
618
  The packaged `agent-timeline` example shows a two-event checkout timeline that an AI assistant can inspect without session replay or payload capture. It combines product action metadata, network milestone metadata, explicit `traceparent` propagation, and a drop-only `eventFilter` that removes low-value info logs:
619
619
 
package/core.cjs CHANGED
@@ -91,6 +91,9 @@ const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
91
91
  // One second matches the OpenTelemetry log processor default, so a lone event becomes readable quickly.
92
92
  const DEFAULT_DELIVERY_INTERVAL_MS = 1000;
93
93
  const MAX_DELIVERY_INTERVAL_MS = 60 * 1000;
94
+ // A rate-limited batch waits from 1 second to 1 hour, plus up to 10 percent jitter, before it is sent again.
95
+ const MIN_RATE_LIMIT_WAIT_MS = 1000;
96
+ const MAX_RATE_LIMIT_WAIT_MS = 60 * 60 * 1000;
94
97
  const DEFAULT_DELIVERY_QUEUE_THRESHOLD = 50;
95
98
  const MAX_CLIENT_REPORT_REASONS = 16;
96
99
  const MAX_CLIENT_REPORT_COUNT = 1_000_000_000;
@@ -208,9 +211,12 @@ class RecordingTransport {
208
211
  }
209
212
 
210
213
  const retryAfterMs = retryAfterMsOrUndefined(next.retryAfterMs);
211
- return retryAfterMs === undefined
212
- ? { statusCode: next.statusCode, attempts: 1 }
213
- : { statusCode: next.statusCode, attempts: 1, retryAfterMs };
214
+ return {
215
+ statusCode: next.statusCode,
216
+ attempts: 1,
217
+ ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
218
+ ...(typeof next.errorCode === "string" ? { errorCode: next.errorCode } : {})
219
+ };
214
220
  }
215
221
  }
216
222
 
@@ -1101,6 +1107,13 @@ class LogBrewClient {
1101
1107
  return;
1102
1108
  }
1103
1109
  if (error instanceof SdkError && error.code === "rate_limited") {
1110
+ if (error.retryable === true) {
1111
+ this.automaticPauseReason = "none";
1112
+ this.retryDelayMs = rateLimitWaitMs(
1113
+ error.retryAfterMs ?? automaticRetryDelayMs(this.deliveryIntervalMs, this.consecutiveDeliveryFailures)
1114
+ );
1115
+ return;
1116
+ }
1104
1117
  this.automaticPauseReason = "rate_limit";
1105
1118
  return;
1106
1119
  }
@@ -1151,7 +1164,9 @@ class LogBrewClient {
1151
1164
  }
1152
1165
  if (response.statusCode === 429) {
1153
1166
  throw new SdkError("rate_limited", "transport rate limited the batch", {
1154
- retryAfterMs: response.retryAfterMs
1167
+ retryAfterMs: response.retryAfterMs,
1168
+ retryable: response.errorCode !== "usage_limit_exceeded"
1169
+ && (retryAfterMsOrUndefined(response.retryAfterMs) !== undefined || response.errorCode === "rate_limited")
1155
1170
  });
1156
1171
  }
1157
1172
  if (response.statusCode >= 200 && response.statusCode < 300) {
@@ -1191,6 +1206,11 @@ function automaticRetryDelayMs(deliveryIntervalMs, consecutiveFailures) {
1191
1206
  return minimumDelay + Math.floor(Math.random() * (maximumDelay - minimumDelay + 1));
1192
1207
  }
1193
1208
 
1209
+ function rateLimitWaitMs(delayMs) {
1210
+ const bounded = Math.min(MAX_RATE_LIMIT_WAIT_MS, Math.max(MIN_RATE_LIMIT_WAIT_MS, delayMs));
1211
+ return Math.min(MAX_RATE_LIMIT_WAIT_MS, bounded + Math.floor(Math.random() * (Math.floor(bounded / 10) + 1)));
1212
+ }
1213
+
1194
1214
  function statusClass(statusCode) {
1195
1215
  if (statusCode >= 200 && statusCode < 300) {
1196
1216
  return "success";
package/index.d.ts CHANGED
@@ -643,10 +643,10 @@ export type TimelineAttributesOptions = {
643
643
  metadata?: Metadata;
644
644
  };
645
645
 
646
- /** Planned backend support-ticket sources accepted by explicit draft helpers. */
646
+ /** Support-ticket sources accepted by explicit draft helpers. */
647
647
  export type SupportTicketSource = "cli" | "sdk" | "website" | "docs" | "mobile";
648
648
 
649
- /** Planned backend support-ticket categories accepted by explicit draft helpers. */
649
+ /** Support-ticket categories accepted by explicit draft helpers. */
650
650
  export type SupportTicketCategory =
651
651
  | "sdk_install_failure"
652
652
  | "ingest_failure"
@@ -686,7 +686,7 @@ export type SupportTicketDraftInput = {
686
686
  diagnostics?: Record<string, unknown>;
687
687
  };
688
688
 
689
- /** Planned backend create payload produced locally for explicit user or agent action. */
689
+ /** Support-ticket create body built locally. It sends nothing. */
690
690
  export type SupportTicketDraft = {
691
691
  source: SupportTicketSource;
692
692
  category: SupportTicketCategory;
@@ -775,6 +775,8 @@ export type TransportResponse = {
775
775
  batches?: number;
776
776
  /** Optional retry delay from a rate-limit response, in milliseconds. */
777
777
  retryAfterMs?: number;
778
+ /** Optional `code` from a 429 response body, such as `rate_limited` or `usage_limit_exceeded`. */
779
+ errorCode?: string;
778
780
  };
779
781
 
780
782
  /** Minimal transport interface accepted by flush and shutdown operations. */
@@ -844,7 +846,7 @@ export declare class TransportError extends Error {
844
846
 
845
847
  /** Scripted transport for previewing, accepting, or failing queued event flushes. */
846
848
  export declare class RecordingTransport {
847
- constructor(scriptedResponses?: Array<{ statusCode: number; retryAfterMs?: number } | Error>);
849
+ constructor(scriptedResponses?: Array<{ statusCode: number; retryAfterMs?: number; errorCode?: string } | Error>);
848
850
  /** Every request body sent through this transport instance. */
849
851
  sentBodies: string[];
850
852
  /** Create a transport that accepts queued flushes with a 202 response. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
@@ -82,6 +82,7 @@
82
82
  "node": ">=18"
83
83
  },
84
84
  "license": "MIT",
85
+ "homepage": "https://docs.logbrew.co",
85
86
  "repository": {
86
87
  "type": "git",
87
88
  "url": "git+https://github.com/LogBrewCo/sdk.git"