@logbrew/sdk 0.1.6 → 0.1.7

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/index.d.ts CHANGED
@@ -2,6 +2,48 @@
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
+ /** Bounded service, runtime, or framework identity shared by telemetry signals. */
6
+ export type TelemetryNamedVersion = {
7
+ name: string;
8
+ version?: string;
9
+ };
10
+ /** Privacy-bounded resource identity shared by telemetry signals. */
11
+ export type TelemetryResource = {
12
+ service?: TelemetryNamedVersion;
13
+ deployment?: { environment?: string; release?: string };
14
+ runtime?: TelemetryNamedVersion;
15
+ framework?: TelemetryNamedVersion;
16
+ operatingSystem?: TelemetryNamedVersion & { build?: string };
17
+ device?: { family?: string; model?: string; architecture?: string };
18
+ application?: { name?: string; version?: string; build?: string };
19
+ };
20
+ /** W3C-compatible correlation identity shared by non-span telemetry. */
21
+ export type TelemetryTraceContext = {
22
+ traceId: string;
23
+ spanId?: string;
24
+ parentSpanId?: string;
25
+ sampled?: boolean;
26
+ };
27
+ /** Opaque application session identity. */
28
+ export type TelemetrySessionContext = {
29
+ id: string;
30
+ previousId?: string;
31
+ };
32
+ /** Explicit app-owned subject identity; do not send names, email addresses, or IP addresses. */
33
+ export type TelemetrySubjectContext = {
34
+ id: string;
35
+ kind: "anonymous" | "user";
36
+ };
37
+ /** Versioned shared context available on every LogBrew event type. */
38
+ export type TelemetryContext = {
39
+ schemaVersion: 1;
40
+ resource?: TelemetryResource;
41
+ trace?: TelemetryTraceContext;
42
+ session?: TelemetrySessionContext;
43
+ subject?: TelemetrySubjectContext;
44
+ /** Up to 32 low-cardinality string dimensions with safe machine keys. */
45
+ tags?: Record<string, string>;
46
+ };
5
47
  /** Canonical user-facing severity categories accepted by LogBrew. */
6
48
  export type Severity = "info" | "warning" | "error" | "critical";
7
49
  /** Runtime-level aliases accepted for compatibility and normalized before send. */
@@ -211,6 +253,7 @@ export type ReleaseAttributes = {
211
253
  commit?: string;
212
254
  notes?: string;
213
255
  metadata?: Metadata;
256
+ context?: TelemetryContext;
214
257
  };
215
258
 
216
259
  /** Public environment event attributes. */
@@ -218,6 +261,7 @@ export type EnvironmentAttributes = {
218
261
  name: string;
219
262
  region?: string;
220
263
  metadata?: Metadata;
264
+ context?: TelemetryContext;
221
265
  };
222
266
 
223
267
  /** Privacy-bounded generated JavaScript frame attached to an issue. */
@@ -228,18 +272,71 @@ export type IssueStackFrame = {
228
272
  line: number;
229
273
  /** One-based generated source column. */
230
274
  column: number;
275
+ /** Optional bounded function or method identity. */
276
+ function?: string;
277
+ /** Optional bounded module, package, or namespace identity. */
278
+ module?: string;
279
+ /** Whether application code classified this frame as app-owned. */
280
+ inApp?: boolean;
231
281
  /** Optional release-artifact Debug ID matched to this generated file. */
232
282
  debugId?: string;
233
283
  };
234
284
 
285
+ /** Runtime path that captured an exception and whether it escaped that path. */
286
+ export type IssueExceptionMechanism = {
287
+ /** Stable low-cardinality capture mechanism, such as `react.error_boundary`. */
288
+ type: string;
289
+ /** False when the exception escaped the capture boundary. */
290
+ handled: boolean;
291
+ };
292
+
293
+ /** Structured exception identity attached to an issue. */
294
+ export type IssueException = {
295
+ /** Bounded runtime exception class or error type. */
296
+ type: string;
297
+ /** Capture mechanism when the SDK can determine it. */
298
+ mechanism?: IssueExceptionMechanism;
299
+ };
300
+
301
+ export type IssueBreadcrumbLevel = "debug" | "info" | "warning" | "error" | "critical";
302
+ export type IssueBreadcrumbLevelInput = IssueBreadcrumbLevel | "trace" | "log" | "warn" | "fatal";
303
+ export type IssueBreadcrumbDataValue = string | number | boolean | null;
304
+
305
+ /** One privacy-bounded step that happened before an issue. */
306
+ export type IssueBreadcrumb = {
307
+ /** RFC 3339 timestamp with an explicit timezone. */
308
+ timestamp: string;
309
+ /** Optional stable breadcrumb kind, such as `navigation` or `http`. */
310
+ type?: string;
311
+ /** Required low-cardinality source category. */
312
+ category: string;
313
+ level?: IssueBreadcrumbLevel;
314
+ /** Optional bounded display-safe description. */
315
+ message?: string;
316
+ /** At most eight flat primitive fields. Never include authentication material or raw request data. */
317
+ data?: Record<string, IssueBreadcrumbDataValue>;
318
+ };
319
+
320
+ /** Input accepted by `addBreadcrumb`; the client supplies the current timestamp when omitted. */
321
+ export type IssueBreadcrumbInput = Omit<IssueBreadcrumb, "timestamp" | "level"> & {
322
+ timestamp?: string;
323
+ level?: IssueBreadcrumbLevelInput;
324
+ };
325
+
235
326
  /** Public issue event attributes. */
236
327
  export type IssueAttributes = {
237
328
  title: string;
238
329
  level: SeverityInput;
239
330
  message?: string;
331
+ exception?: IssueException;
240
332
  /** Ordered privacy-bounded generated frames, capped at 32. */
241
333
  stackFrames?: IssueStackFrame[];
334
+ /** Oldest-to-newest issue history, capped at the most recent 64 entries. */
335
+ breadcrumbs?: IssueBreadcrumb[];
336
+ /** True when older or invalid history was omitted before capture. */
337
+ breadcrumbsTruncated?: boolean;
242
338
  metadata?: Metadata;
339
+ context?: TelemetryContext;
243
340
  };
244
341
 
245
342
  /** Options for creating privacy-bounded issue attributes from a JavaScript error. */
@@ -248,6 +345,10 @@ export type JavaScriptErrorIssueOptions = {
248
345
  level?: SeverityInput;
249
346
  message?: string;
250
347
  metadata?: Metadata;
348
+ /** Stable low-cardinality capture mechanism. Defaults to `javascript.error`. */
349
+ mechanism?: string;
350
+ /** Whether the error was handled by the capture boundary. Defaults to true. */
351
+ handled?: boolean;
251
352
  /** Metadata source label. Defaults to `javascript.error`. */
252
353
  source?: string;
253
354
  /** Active trace copied into primitive correlation metadata. */
@@ -276,6 +377,7 @@ export type LogAttributes = {
276
377
  level: SeverityInput;
277
378
  logger?: string;
278
379
  metadata?: Metadata;
380
+ context?: TelemetryContext;
279
381
  };
280
382
 
281
383
  /** Console method names supported by the opt-in console capture helper. */
@@ -406,6 +508,7 @@ export type SpanAttributes = {
406
508
  events?: SpanEventSummary[];
407
509
  links?: SpanLinkSummary[];
408
510
  metadata?: Metadata;
511
+ context?: TelemetryContext;
409
512
  };
410
513
 
411
514
  /** Public action event attributes. */
@@ -413,8 +516,18 @@ export type ActionAttributes = {
413
516
  name: string;
414
517
  status: "queued" | "running" | "success" | "failure";
415
518
  metadata?: Metadata;
519
+ context?: TelemetryContext;
416
520
  };
417
521
 
522
+ /** Stable product-analytics event categories carried in reserved action metadata. */
523
+ export type ProductAnalyticsKind = "page_view" | "screen_view" | "interaction";
524
+
525
+ /** Current version of the reserved product-analytics metadata vocabulary. */
526
+ export declare const PRODUCT_ANALYTICS_SCHEMA_VERSION: 1;
527
+
528
+ /** Product-analytics categories understood by this SDK version. */
529
+ export declare const PRODUCT_ANALYTICS_KINDS: readonly ProductAnalyticsKind[];
530
+
418
531
  /** App-owned product step input for agent-readable action timelines. */
419
532
  export type ProductActionInput = string | {
420
533
  name: string;
@@ -515,6 +628,7 @@ export type MetricAttributes = {
515
628
  unit: string;
516
629
  temporality: "delta" | "cumulative";
517
630
  metadata?: Metadata;
631
+ context?: TelemetryContext;
518
632
  } | {
519
633
  name: string;
520
634
  kind: "gauge";
@@ -656,6 +770,8 @@ export declare class LogBrewClient {
656
770
  apiKey: string;
657
771
  sdkName: string;
658
772
  sdkVersion: string;
773
+ /** Versioned context merged into every captured event; event context can override dynamic fields. */
774
+ context?: TelemetryContext;
659
775
  /** Retry attempts after the first send. Must be a non-negative integer; defaults to 2. */
660
776
  maxRetries?: number;
661
777
  eventFilter?: EventFilter;
@@ -690,6 +806,10 @@ export declare class LogBrewClient {
690
806
  previewJson(): string;
691
807
  /** Purge queued events from memory and persistence while no delivery operation is active. */
692
808
  purgePendingEvents(): number;
809
+ /** Add one explicit privacy-bounded breadcrumb to the client's 64-entry issue history. */
810
+ addBreadcrumb(breadcrumb: IssueBreadcrumbInput, timestamp?: string): void;
811
+ /** Clear the current issue breadcrumb history and return the number removed. */
812
+ clearBreadcrumbs(): number;
693
813
  release(id: string, timestamp: string, attributes: ReleaseAttributes): void;
694
814
  environment(id: string, timestamp: string, attributes: EnvironmentAttributes): void;
695
815
  issue(id: string, timestamp: string, attributes: IssueAttributes): void;
package/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import sdk from "./index.cjs";
2
2
 
3
3
  export const {
4
+ PRODUCT_ANALYTICS_KINDS,
5
+ PRODUCT_ANALYTICS_SCHEMA_VERSION,
4
6
  createBaggage,
5
7
  createIssueAttributesFromError,
6
8
  createNetworkMilestoneAttributes,
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+
3
+ const MAX_ISSUE_BREADCRUMBS = 64;
4
+ const MAX_EXCEPTION_TYPE_LENGTH = 256;
5
+ const MAX_MECHANISM_TYPE_LENGTH = 64;
6
+ const MAX_BREADCRUMB_NAME_LENGTH = 64;
7
+ const MAX_BREADCRUMB_MESSAGE_LENGTH = 512;
8
+ const MAX_BREADCRUMB_DATA_FIELDS = 8;
9
+ const MAX_BREADCRUMB_DATA_STRING_LENGTH = 256;
10
+ const MACHINE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/u;
11
+ const DATA_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u;
12
+ const BREADCRUMB_LEVEL_ALIASES = new Map([
13
+ ["trace", "debug"],
14
+ ["debug", "debug"],
15
+ ["info", "info"],
16
+ ["log", "info"],
17
+ ["warn", "warning"],
18
+ ["warning", "warning"],
19
+ ["error", "error"],
20
+ ["fatal", "critical"],
21
+ ["critical", "critical"]
22
+ ]);
23
+
24
+ function buildIssueDiagnosticsHelpers({ SdkError, requireTimestamp }) {
25
+ function validationError(message) {
26
+ return new SdkError("validation_error", message);
27
+ }
28
+
29
+ function validateIssueException(exception) {
30
+ if (exception === undefined) {
31
+ return undefined;
32
+ }
33
+ requireObject("issue exception", exception);
34
+ rejectUnknownKeys("issue exception", exception, new Set(["type", "mechanism"]));
35
+ const type = boundedText("issue exception type", exception.type, MAX_EXCEPTION_TYPE_LENGTH, {
36
+ rejectLocationText: true
37
+ });
38
+ const mechanism = validateIssueExceptionMechanism(exception.mechanism);
39
+ return {
40
+ type,
41
+ ...(mechanism === undefined ? {} : { mechanism })
42
+ };
43
+ }
44
+
45
+ function validateIssueExceptionMechanism(mechanism) {
46
+ if (mechanism === undefined) {
47
+ return undefined;
48
+ }
49
+ requireObject("issue exception mechanism", mechanism);
50
+ rejectUnknownKeys(
51
+ "issue exception mechanism",
52
+ mechanism,
53
+ new Set(["type", "handled"])
54
+ );
55
+ if (typeof mechanism.type !== "string" || !MACHINE_NAME_PATTERN.test(mechanism.type)) {
56
+ throw validationError(
57
+ `issue exception mechanism type must match ${MACHINE_NAME_PATTERN}`
58
+ );
59
+ }
60
+ if (Array.from(mechanism.type).length > MAX_MECHANISM_TYPE_LENGTH) {
61
+ throw validationError(
62
+ `issue exception mechanism type must be at most ${MAX_MECHANISM_TYPE_LENGTH} characters`
63
+ );
64
+ }
65
+ if (typeof mechanism.handled !== "boolean") {
66
+ throw validationError("issue exception mechanism handled must be a boolean");
67
+ }
68
+ return { type: mechanism.type, handled: mechanism.handled };
69
+ }
70
+
71
+ function createIssueException(type, mechanism, handled) {
72
+ return validateIssueException({
73
+ type,
74
+ mechanism: { type: mechanism, handled }
75
+ });
76
+ }
77
+
78
+ function validateIssueBreadcrumb(breadcrumb, defaultTimestamp) {
79
+ requireObject("issue breadcrumb", breadcrumb);
80
+ rejectUnknownKeys(
81
+ "issue breadcrumb",
82
+ breadcrumb,
83
+ new Set(["timestamp", "type", "category", "level", "message", "data"])
84
+ );
85
+ const timestamp = breadcrumb.timestamp ?? defaultTimestamp;
86
+ requireTimestamp(timestamp);
87
+ if (typeof breadcrumb.category !== "string" || !MACHINE_NAME_PATTERN.test(breadcrumb.category)) {
88
+ throw validationError(`issue breadcrumb category must match ${MACHINE_NAME_PATTERN}`);
89
+ }
90
+ const type = optionalMachineName("issue breadcrumb type", breadcrumb.type);
91
+ const level = optionalBreadcrumbLevel(breadcrumb.level);
92
+ const message = breadcrumb.message === undefined
93
+ ? undefined
94
+ : boundedText(
95
+ "issue breadcrumb message",
96
+ breadcrumb.message,
97
+ MAX_BREADCRUMB_MESSAGE_LENGTH
98
+ );
99
+ const data = validateBreadcrumbData(breadcrumb.data);
100
+ return {
101
+ timestamp,
102
+ ...(type === undefined ? {} : { type }),
103
+ category: breadcrumb.category,
104
+ ...(level === undefined ? {} : { level }),
105
+ ...(message === undefined ? {} : { message }),
106
+ ...(data === undefined ? {} : { data })
107
+ };
108
+ }
109
+
110
+ function validateIssueBreadcrumbs(breadcrumbs) {
111
+ if (breadcrumbs === undefined) {
112
+ return undefined;
113
+ }
114
+ if (!Array.isArray(breadcrumbs) || breadcrumbs.length < 1 || breadcrumbs.length > MAX_ISSUE_BREADCRUMBS) {
115
+ throw validationError(
116
+ `issue breadcrumbs must contain 1-${MAX_ISSUE_BREADCRUMBS} entries`
117
+ );
118
+ }
119
+ return breadcrumbs.map((breadcrumb) => validateIssueBreadcrumb(breadcrumb));
120
+ }
121
+
122
+ function validateIssueDiagnostics(attributes) {
123
+ const exception = validateIssueException(attributes.exception);
124
+ const breadcrumbs = validateIssueBreadcrumbs(attributes.breadcrumbs);
125
+ if (
126
+ attributes.breadcrumbsTruncated !== undefined
127
+ && typeof attributes.breadcrumbsTruncated !== "boolean"
128
+ ) {
129
+ throw validationError("issue breadcrumbsTruncated must be a boolean");
130
+ }
131
+ return {
132
+ ...(exception === undefined ? {} : { exception }),
133
+ ...(breadcrumbs === undefined ? {} : { breadcrumbs }),
134
+ ...(attributes.breadcrumbsTruncated === true ? { breadcrumbsTruncated: true } : {})
135
+ };
136
+ }
137
+
138
+ function cloneIssueDiagnostics(attributes) {
139
+ const diagnostics = {};
140
+ if (attributes.exception !== undefined) {
141
+ diagnostics.exception = {
142
+ ...attributes.exception,
143
+ ...(attributes.exception.mechanism === undefined
144
+ ? {}
145
+ : { mechanism: { ...attributes.exception.mechanism } })
146
+ };
147
+ }
148
+ if (Array.isArray(attributes.breadcrumbs)) {
149
+ diagnostics.breadcrumbs = attributes.breadcrumbs.map((breadcrumb) => ({
150
+ ...breadcrumb,
151
+ ...(breadcrumb.data === undefined ? {} : { data: { ...breadcrumb.data } })
152
+ }));
153
+ }
154
+ if (attributes.breadcrumbsTruncated === true) {
155
+ diagnostics.breadcrumbsTruncated = true;
156
+ }
157
+ return diagnostics;
158
+ }
159
+
160
+ function validateBreadcrumbData(data) {
161
+ if (data === undefined) {
162
+ return undefined;
163
+ }
164
+ requireObject("issue breadcrumb data", data);
165
+ const entries = Object.entries(data);
166
+ if (entries.length > MAX_BREADCRUMB_DATA_FIELDS) {
167
+ throw validationError(
168
+ `issue breadcrumb data must contain at most ${MAX_BREADCRUMB_DATA_FIELDS} fields`
169
+ );
170
+ }
171
+ const validated = {};
172
+ for (const [key, value] of entries) {
173
+ if (!DATA_KEY_PATTERN.test(key)) {
174
+ throw validationError(`issue breadcrumb data key must match ${DATA_KEY_PATTERN}`);
175
+ }
176
+ if (typeof value === "string") {
177
+ validated[key] = boundedText(
178
+ `issue breadcrumb data value for ${key}`,
179
+ value,
180
+ MAX_BREADCRUMB_DATA_STRING_LENGTH
181
+ );
182
+ } else if (typeof value === "number") {
183
+ if (!Number.isFinite(value)) {
184
+ throw validationError(`issue breadcrumb data value for ${key} must be finite`);
185
+ }
186
+ validated[key] = value;
187
+ } else if (typeof value === "boolean" || value === null) {
188
+ validated[key] = value;
189
+ } else {
190
+ throw validationError(
191
+ `issue breadcrumb data value for ${key} must be a string, number, boolean, or null`
192
+ );
193
+ }
194
+ }
195
+ return validated;
196
+ }
197
+
198
+ function optionalMachineName(label, value) {
199
+ if (value === undefined) {
200
+ return undefined;
201
+ }
202
+ if (typeof value !== "string" || !MACHINE_NAME_PATTERN.test(value)) {
203
+ throw validationError(`${label} must match ${MACHINE_NAME_PATTERN}`);
204
+ }
205
+ if (Array.from(value).length > MAX_BREADCRUMB_NAME_LENGTH) {
206
+ throw validationError(`${label} must be at most ${MAX_BREADCRUMB_NAME_LENGTH} characters`);
207
+ }
208
+ return value;
209
+ }
210
+
211
+ function optionalBreadcrumbLevel(value) {
212
+ if (value === undefined) {
213
+ return undefined;
214
+ }
215
+ const normalized = typeof value === "string" ? BREADCRUMB_LEVEL_ALIASES.get(value) : undefined;
216
+ if (normalized === undefined) {
217
+ throw validationError(
218
+ `issue breadcrumb level must be one of: ${Array.from(BREADCRUMB_LEVEL_ALIASES.keys()).join(", ")}`
219
+ );
220
+ }
221
+ return normalized;
222
+ }
223
+
224
+ function boundedText(label, value, maxLength, { rejectLocationText = false } = {}) {
225
+ if (typeof value !== "string" || value.trim() === "") {
226
+ throw validationError(`${label} must be non-empty`);
227
+ }
228
+ if (
229
+ Array.from(value).length > maxLength
230
+ || hasControlCharacter(value)
231
+ || (rejectLocationText && /[?#]/u.test(value))
232
+ ) {
233
+ throw validationError(`${label} is invalid or exceeds ${maxLength} characters`);
234
+ }
235
+ return value;
236
+ }
237
+
238
+ function requireObject(label, value) {
239
+ if (!value || Array.isArray(value) || typeof value !== "object") {
240
+ throw validationError(`${label} must be an object`);
241
+ }
242
+ }
243
+
244
+ function rejectUnknownKeys(label, value, allowed) {
245
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
246
+ if (unknown.length > 0) {
247
+ throw validationError(`${label} has unsupported fields: ${unknown.sort().join(", ")}`);
248
+ }
249
+ }
250
+
251
+ function hasControlCharacter(value) {
252
+ return Array.from(value).some((character) => {
253
+ const code = character.codePointAt(0);
254
+ return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
255
+ });
256
+ }
257
+
258
+ return {
259
+ MAX_ISSUE_BREADCRUMBS,
260
+ cloneIssueDiagnostics,
261
+ createIssueException,
262
+ validateIssueBreadcrumb,
263
+ validateIssueDiagnostics
264
+ };
265
+ }
266
+
267
+ module.exports = { buildIssueDiagnosticsHelpers };
package/issue-stack.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  const MAX_ISSUE_STACK_FRAMES = 32;
4
+ const MAX_ISSUE_STACK_FUNCTION_LENGTH = 256;
5
+ const MAX_ISSUE_STACK_MODULE_LENGTH = 512;
4
6
  const SAFE_DEBUG_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
5
7
  const LOCAL_ABSOLUTE_PATH_PATTERN = /^(?:\/(?:Users|home|private|tmp|var|Volumes)\/|[A-Za-z]:[\\/])/u;
6
8
 
@@ -54,7 +56,31 @@ function buildIssueStackHelpers({ SdkError }) {
54
56
  if (debugId === null) {
55
57
  throw new SdkError("validation_error", "issue stack frame debugId is invalid");
56
58
  }
57
- return { filename, line, column, ...(debugId ? { debugId } : {}) };
59
+ const functionName = optionalFrameIdentity(frame.function, MAX_ISSUE_STACK_FUNCTION_LENGTH);
60
+ if (functionName === null) {
61
+ throw new SdkError("validation_error", "issue stack frame function is invalid");
62
+ }
63
+ const moduleName = optionalFrameIdentity(frame.module, MAX_ISSUE_STACK_MODULE_LENGTH, true);
64
+ if (moduleName === null) {
65
+ throw new SdkError("validation_error", "issue stack frame module is invalid");
66
+ }
67
+ const inApp = frame.inApp === undefined
68
+ ? undefined
69
+ : typeof frame.inApp === "boolean"
70
+ ? frame.inApp
71
+ : null;
72
+ if (inApp === null) {
73
+ throw new SdkError("validation_error", "issue stack frame inApp is invalid");
74
+ }
75
+ return {
76
+ filename,
77
+ line,
78
+ column,
79
+ ...(functionName ? { function: functionName } : {}),
80
+ ...(moduleName ? { module: moduleName } : {}),
81
+ ...(inApp !== undefined ? { inApp } : {}),
82
+ ...(debugId ? { debugId } : {})
83
+ };
58
84
  });
59
85
  }
60
86
 
@@ -67,13 +93,18 @@ function parseJavaScriptStackFrame(rawLine) {
67
93
  return null;
68
94
  }
69
95
  let location = line;
96
+ let functionName;
70
97
  if (location.startsWith("at ")) {
71
98
  location = location.slice(3).trim();
72
99
  if (location.endsWith(")") && location.includes("(")) {
73
- location = location.slice(location.lastIndexOf("(") + 1, -1);
100
+ const marker = location.lastIndexOf("(");
101
+ functionName = generatedFrameFunction(location.slice(0, marker));
102
+ location = location.slice(marker + 1, -1);
74
103
  }
75
104
  } else if (location.includes("@")) {
76
- location = location.slice(location.lastIndexOf("@") + 1);
105
+ const marker = location.lastIndexOf("@");
106
+ functionName = generatedFrameFunction(location.slice(0, marker));
107
+ location = location.slice(marker + 1);
77
108
  }
78
109
  const parts = location.split(":");
79
110
  if (parts.length < 3) {
@@ -85,7 +116,42 @@ function parseJavaScriptStackFrame(rawLine) {
85
116
  if (!filename || lineNumber === null || column === null) {
86
117
  return null;
87
118
  }
88
- return { filename, line: lineNumber, column };
119
+ return {
120
+ filename,
121
+ line: lineNumber,
122
+ column,
123
+ ...(functionName ? { function: functionName } : {})
124
+ };
125
+ }
126
+
127
+ function optionalFrameIdentity(value, maxLength, rejectLocationText = false) {
128
+ if (value === undefined) {
129
+ return undefined;
130
+ }
131
+ if (typeof value !== "string") {
132
+ return null;
133
+ }
134
+ const identity = value.trim();
135
+ if (!identity
136
+ || Array.from(identity).length > maxLength
137
+ || hasControlCharacter(identity)
138
+ || (rejectLocationText && (identity.includes("?") || identity.includes("#")))) {
139
+ return null;
140
+ }
141
+ return identity;
142
+ }
143
+
144
+ function generatedFrameFunction(value) {
145
+ const functionName = optionalFrameIdentity(value, MAX_ISSUE_STACK_FUNCTION_LENGTH);
146
+ if (!functionName
147
+ || functionName.includes("@")
148
+ || functionName.includes("/")
149
+ || functionName.includes("\\")
150
+ || functionName.includes("?")
151
+ || functionName.includes("#")) {
152
+ return undefined;
153
+ }
154
+ return functionName;
89
155
  }
90
156
 
91
157
  function positiveIntegerFromText(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
@@ -43,12 +43,14 @@
43
43
  "index.js",
44
44
  "index.d.ts",
45
45
  "index.d.cts",
46
+ "issue-diagnostics.cjs",
46
47
  "issue-stack.cjs",
47
48
  "log-context.cjs",
48
49
  "opentelemetry.cjs",
49
50
  "react-native.js",
50
51
  "react-native.d.ts",
51
52
  "support-ticket.cjs",
53
+ "telemetry-context.cjs",
52
54
  "trace-context.cjs",
53
55
  "winston.cjs",
54
56
  "release-artifacts-common.js",
package/react-native.d.ts CHANGED
@@ -40,6 +40,7 @@ export type {
40
40
  PinoDestinationConfig,
41
41
  PinoDestinationHandle,
42
42
  PinoLogRecord,
43
+ ProductAnalyticsKind,
43
44
  ProductActionInput,
44
45
  ReleaseAttributes,
45
46
  Severity,
@@ -65,6 +66,8 @@ export type {
65
66
  } from "./index.js";
66
67
 
67
68
  export {
69
+ PRODUCT_ANALYTICS_KINDS,
70
+ PRODUCT_ANALYTICS_SCHEMA_VERSION,
68
71
  createBaggage,
69
72
  createIssueAttributesFromError,
70
73
  createLogBrewOpenTelemetrySpanExporter,
package/react-native.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import sdk from "./core.cjs";
2
2
 
3
3
  export const {
4
+ PRODUCT_ANALYTICS_KINDS,
5
+ PRODUCT_ANALYTICS_SCHEMA_VERSION,
4
6
  createBaggage,
5
7
  createIssueAttributesFromError,
6
8
  createNetworkMilestoneAttributes,