@guidelytech/node 0.15.0 → 0.17.0

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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -24,7 +34,9 @@ __export(index_exports, {
24
34
  Guidely: () => Guidely,
25
35
  GuidelyError: () => GuidelyError,
26
36
  SDK_NAME: () => SDK_NAME,
27
- SDK_VERSION: () => SDK_VERSION
37
+ SDK_VERSION: () => SDK_VERSION,
38
+ Scope: () => Scope,
39
+ parseStack: () => parseStack
28
40
  });
29
41
  module.exports = __toCommonJS(index_exports);
30
42
 
@@ -76,6 +88,11 @@ function id(name, value, required) {
76
88
  if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);
77
89
  return s;
78
90
  }
91
+ function version(value) {
92
+ if (typeof value !== "string" && typeof value !== "number") return void 0;
93
+ const s = String(value).trim();
94
+ return s ? s.slice(0, 200) : void 0;
95
+ }
79
96
  function isPlainObject(v) {
80
97
  if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
81
98
  const proto = Object.getPrototypeOf(v);
@@ -86,11 +103,11 @@ function object(name, value) {
86
103
  if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);
87
104
  return value;
88
105
  }
89
- function buildMessage(type, args, now = () => /* @__PURE__ */ new Date()) {
90
- if (!isPlainObject(args)) throw new InvalidMessageError(`${type}() needs an object argument`);
106
+ function buildMessage(type2, args, now = () => /* @__PURE__ */ new Date()) {
107
+ if (!isPlainObject(args)) throw new InvalidMessageError(`${type2}() needs an object argument`);
91
108
  const timestamp = args.timestamp === void 0 || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp);
92
- const base = { type, messageId: newMessageId(), timestamp };
93
- switch (type) {
109
+ const base = { type: type2, messageId: newMessageId(), timestamp };
110
+ switch (type2) {
94
111
  case "identify": {
95
112
  const userId = id("userId", args.userId, true);
96
113
  const traits = object("traits", args.traits);
@@ -106,13 +123,15 @@ function buildMessage(type, args, now = () => /* @__PURE__ */ new Date()) {
106
123
  if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);
107
124
  const properties = object("properties", args.properties);
108
125
  const accountId = id("accountId", args.accountId, false);
126
+ const appVersion = version(args.appVersion);
109
127
  return {
110
128
  ...base,
111
129
  ...userId ? { userId } : {},
112
130
  ...anonymousId ? { anonymousId } : {},
113
131
  event,
114
132
  ...properties ? { properties } : {},
115
- ...accountId ? { accountId } : {}
133
+ ...accountId ? { accountId } : {},
134
+ ...appVersion ? { appVersion } : {}
116
135
  };
117
136
  }
118
137
  case "group": {
@@ -145,12 +164,1249 @@ function jitter(delayMs, random) {
145
164
  return Math.round(delayMs * (0.8 + 0.4 * random));
146
165
  }
147
166
 
167
+ // src/errors/event.ts
168
+ var import_node_crypto2 = require("crypto");
169
+ var import_node_fs = require("fs");
170
+ var os = __toESM(require("os"), 1);
171
+
172
+ // src/errors/normalize.ts
173
+ var SENSITIVE_KEY = /(password|passwd|secret|token|credit|card|cvv|ssn|iban|authorization|cookie|api[-_]?key|private[-_]?key)/i;
174
+ var SENSITIVE_HEADER = /^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token|x-csrf-token|x-xsrf-token|x-guidely-key|x-guidely-conversation)$|secret|token|password|passwd|api[-_]?key|signature|session/i;
175
+ var SENSITIVE_PARAM = /(token|key|secret|password|passwd|pwd|auth|session|code|sig|signature|credential|ssn|card|cvv|iban)/i;
176
+ var IP_HEADER = /^(x-forwarded-for|x-real-ip|forwarded|cf-connecting-ip|true-client-ip|x-client-ip)$/i;
177
+ var FILTERED = "[Filtered]";
178
+ var MAX_STRING = 2e3;
179
+ var MAX_KEYS = 100;
180
+ var MAX_ITEMS = 100;
181
+ function normalize(value, depth = 4, seen = /* @__PURE__ */ new WeakSet()) {
182
+ switch (typeof value) {
183
+ case "string":
184
+ return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}\u2026` : value;
185
+ case "number":
186
+ return Number.isFinite(value) ? value : String(value);
187
+ case "boolean":
188
+ return value;
189
+ case "bigint":
190
+ return `${value}n`;
191
+ case "symbol":
192
+ return value.toString();
193
+ case "function":
194
+ return `[Function${value.name ? `: ${value.name}` : ""}]`;
195
+ case "undefined":
196
+ return void 0;
197
+ }
198
+ if (value === null) return null;
199
+ const obj = value;
200
+ if (obj instanceof Date) return Number.isNaN(obj.getTime()) ? "Invalid Date" : obj.toISOString();
201
+ if (obj instanceof Error) return `${obj.name}: ${obj.message}`.slice(0, MAX_STRING);
202
+ if (obj instanceof RegExp) return obj.toString();
203
+ if (ArrayBuffer.isView(obj)) return `[${obj.constructor?.name ?? "Buffer"} ${obj.byteLength} bytes]`;
204
+ if (seen.has(obj)) return "[Circular]";
205
+ if (depth <= 0) return Array.isArray(obj) ? "[Array]" : "[Object]";
206
+ seen.add(obj);
207
+ try {
208
+ if (Array.isArray(obj) || obj instanceof Set) {
209
+ const items = Array.isArray(obj) ? obj : [...obj];
210
+ const out2 = items.slice(0, MAX_ITEMS).map((v) => normalize(v, depth - 1, seen) ?? null);
211
+ if (items.length > MAX_ITEMS) out2.push(`[${items.length - MAX_ITEMS} more]`);
212
+ return out2;
213
+ }
214
+ const entries = obj instanceof Map ? [...obj].map(([k, v]) => [String(k), v]) : Object.keys(obj).map((k) => [k, void 0]);
215
+ const out = {};
216
+ let n = 0;
217
+ for (const [key, mapped] of entries) {
218
+ if (n >= MAX_KEYS) {
219
+ out["\u2026"] = `${entries.length - MAX_KEYS} more keys`;
220
+ break;
221
+ }
222
+ if (SENSITIVE_KEY.test(key)) continue;
223
+ let v;
224
+ try {
225
+ v = obj instanceof Map ? mapped : obj[key];
226
+ } catch {
227
+ v = "[Getter threw]";
228
+ }
229
+ const nv = normalize(v, depth - 1, seen);
230
+ if (nv === void 0) continue;
231
+ out[key] = nv;
232
+ n++;
233
+ }
234
+ return out;
235
+ } finally {
236
+ seen.delete(obj);
237
+ }
238
+ }
239
+ function normalizeObject(value, depth = 4) {
240
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
241
+ const out = normalize(value, depth);
242
+ return out && typeof out === "object" && !Array.isArray(out) ? out : void 0;
243
+ }
244
+ function scrubQuery(query) {
245
+ return query.replace(/^\?/, "").split("&").filter(Boolean).slice(0, 100).map((pair) => {
246
+ const i = pair.indexOf("=");
247
+ const key = i < 0 ? pair : pair.slice(0, i);
248
+ let name = key;
249
+ try {
250
+ name = decodeURIComponent(key.replace(/\+/g, " "));
251
+ } catch {
252
+ }
253
+ return i >= 0 && SENSITIVE_PARAM.test(name) ? `${key}=${FILTERED}` : pair;
254
+ }).join("&").slice(0, 2e3);
255
+ }
256
+ function stripQuery(url) {
257
+ const i = url.search(/[?#]/);
258
+ return (i < 0 ? url : url.slice(0, i)).slice(0, 2e3);
259
+ }
260
+
261
+ // src/errors/stack.ts
262
+ var import_node_path = require("path");
263
+ var import_node_url = require("url");
264
+ var MAX_DETAIL_FRAMES = 250;
265
+ var MAX_PROPERTY_FRAMES = 30;
266
+ var LINE = /^\s*at (?:async )?(?:(.+?) \((.*)\)|(.+))$/;
267
+ var LOCATION = /^(.*?):(\d+)(?::(\d+))?$/;
268
+ var EVAL_ORIGIN = /\(([^()]*?:\d+:\d+)\)/;
269
+ function parseStack(stack, o = {}) {
270
+ if (typeof stack !== "string" || !stack) return [];
271
+ const frames = [];
272
+ for (const raw of stack.split("\n")) {
273
+ if (frames.length >= MAX_DETAIL_FRAMES * 2) break;
274
+ const m = LINE.exec(raw.length > 2e3 ? raw.slice(0, 2e3) : raw);
275
+ if (!m) continue;
276
+ const fn = m[1]?.trim();
277
+ let location = (m[2] ?? m[3] ?? "").trim();
278
+ if (location.startsWith("eval at ")) location = EVAL_ORIGIN.exec(location)?.[1] ?? location;
279
+ const at = LOCATION.exec(location);
280
+ const frame = {};
281
+ if (fn) frame.function = fn.slice(0, 200);
282
+ if (at) {
283
+ const file = filePath(at[1]);
284
+ frame.filename = file.slice(0, 1e3);
285
+ frame.abs_path = file.slice(0, 1e3);
286
+ frame.lineno = Number(at[2]);
287
+ if (at[3]) frame.colno = Number(at[3]);
288
+ const mod = moduleOf(file, o.root);
289
+ if (mod) frame.module = mod;
290
+ frame.in_app = isInApp(file, o.inAppExclude);
291
+ } else {
292
+ if (!fn && !location) continue;
293
+ if (location && !/^index \d+$/.test(location)) frame.filename = location.slice(0, 200);
294
+ frame.in_app = false;
295
+ }
296
+ frames.push(frame);
297
+ }
298
+ frames.reverse();
299
+ return frames.length > MAX_DETAIL_FRAMES ? frames.slice(-MAX_DETAIL_FRAMES) : frames;
300
+ }
301
+ function filePath(location) {
302
+ if (!location.startsWith("file://")) return location;
303
+ try {
304
+ return (0, import_node_url.fileURLToPath)(location);
305
+ } catch {
306
+ return location;
307
+ }
308
+ }
309
+ function isInApp(file, exclude) {
310
+ if (!file || file.startsWith("node:") || file.startsWith("internal/")) return false;
311
+ if (!file.startsWith("/") && !/^[a-z]:[\\/]/i.test(file) && !file.startsWith("\\\\")) return false;
312
+ if (/[\\/]node_modules[\\/]/.test(file)) return false;
313
+ for (const p of exclude ?? []) {
314
+ if (typeof p === "string") {
315
+ if (p && file.includes(p)) return false;
316
+ } else if (p instanceof RegExp) {
317
+ p.lastIndex = 0;
318
+ if (p.test(file)) return false;
319
+ }
320
+ }
321
+ return true;
322
+ }
323
+ function moduleOf(file, root) {
324
+ if (!file || file.startsWith("node:") || file.startsWith("internal/")) return void 0;
325
+ const normalized = file.replace(/^[a-z]:/i, "").replace(/\\/g, "/");
326
+ if (!normalized.startsWith("/")) return void 0;
327
+ const parsed = import_node_path.posix.parse(normalized);
328
+ let name = /^\.[cm]?[jt]sx?$/.test(parsed.ext) ? parsed.name : parsed.base;
329
+ try {
330
+ name = decodeURIComponent(name);
331
+ } catch {
332
+ }
333
+ const dir = parsed.dir || ".";
334
+ const n = dir.lastIndexOf("/node_modules");
335
+ if (n > -1) return `${dir.slice(n + 14).replace(/\//g, ".")}:${name}`;
336
+ const base = root?.replace(/^[a-z]:/i, "").replace(/\\/g, "/").replace(/\/+$/, "");
337
+ if (base && (dir === base || dir.startsWith(`${base}/`))) {
338
+ const mod = dir.slice(base.length + 1).replace(/\//g, ".");
339
+ return mod ? `${mod}:${name}` : name;
340
+ }
341
+ return name;
342
+ }
343
+ function toGuidelyFrames(frames) {
344
+ const out = [];
345
+ if (!frames) return out;
346
+ for (let i = frames.length - 1; i >= 0 && out.length < MAX_PROPERTY_FRAMES; i--) {
347
+ const f = frames[i];
348
+ const file = f.abs_path || f.filename;
349
+ out.push({
350
+ ...f.function ? { fn: f.function.slice(0, 200) } : {},
351
+ ...file ? { file: file.slice(0, 500) } : {},
352
+ ...typeof f.lineno === "number" ? { line: f.lineno } : {},
353
+ ...typeof f.colno === "number" ? { col: f.colno } : {},
354
+ ...typeof f.in_app === "boolean" ? { inApp: f.in_app } : {}
355
+ });
356
+ }
357
+ return out;
358
+ }
359
+
360
+ // src/errors/event.ts
361
+ var ERROR_LIMITS = {
362
+ /** `detail` (the Sentry-shaped event) after trimming. */
363
+ maxDetailBytes: 65536,
364
+ /** `properties`, inside the event's property budget. */
365
+ maxPropertyBytes: 8192,
366
+ /** A whole `exception` message: properties + detail + envelope. Larger ones lose their `detail`. */
367
+ maxMessageBytes: 81920,
368
+ /** Exceptions in a cause / AggregateError chain. */
369
+ maxChain: 20
370
+ };
371
+ var newEventId = () => (0, import_node_crypto2.randomUUID)().replace(/-/g, "");
372
+ var errorLike = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && (typeof v.stack === "string" || typeof v.name === "string");
373
+ function typeOf(e) {
374
+ const name = typeof e.name === "string" && e.name ? e.name : void 0;
375
+ const ctor = e.constructor?.name;
376
+ return (name === "Error" && typeof ctor === "string" && ctor && ctor !== "Error" ? ctor : name) ?? (typeof ctor === "string" && ctor ? ctor : "Error");
377
+ }
378
+ function describe(value) {
379
+ if (typeof value === "string") return value;
380
+ if (value === void 0 || value === null || typeof value !== "object") return String(value);
381
+ const keys = Object.keys(value).slice(0, 10);
382
+ return keys.length ? `Non-Error exception captured with keys: ${keys.join(", ")}` : `Non-Error exception captured: ${Object.prototype.toString.call(value)}`;
383
+ }
384
+ function exceptionChain(error, mechanism, o, fallbackType = "Error") {
385
+ const out = [];
386
+ const seen = /* @__PURE__ */ new Set();
387
+ const visit = (value, mech) => {
388
+ if (out.length >= ERROR_LIMITS.maxChain) return;
389
+ seen.add(value);
390
+ const id2 = out.length;
391
+ const isError = errorLike(value);
392
+ const frames = isError ? parseStack(value.stack, o) : [];
393
+ const aggregate = isError && Array.isArray(value.errors);
394
+ const ex = {
395
+ type: (isError ? typeOf(value) : fallbackType).slice(0, 200),
396
+ value: (isError ? String(value.message ?? "") : describe(value)).slice(0, 8e3),
397
+ mechanism: { ...mech, exception_id: id2, ...aggregate ? { is_exception_group: true } : {} },
398
+ ...frames.length ? { stacktrace: { frames } } : {}
399
+ };
400
+ out.push(ex);
401
+ if (!isError) return;
402
+ const link = (child, source) => {
403
+ if (!errorLike(child) || seen.has(child)) return;
404
+ visit(child, { type: "chained", handled: mechanism.handled, source, parent_id: id2 });
405
+ };
406
+ if (aggregate) value.errors.slice(0, ERROR_LIMITS.maxChain).forEach((child, i) => link(child, `errors[${i}]`));
407
+ link(value.cause, "cause");
408
+ };
409
+ visit(error, { type: mechanism.type, handled: mechanism.handled });
410
+ return out.reverse();
411
+ }
412
+ function linuxRelease() {
413
+ try {
414
+ const text = (0, import_node_fs.readFileSync)("/etc/os-release", "utf8").slice(0, 1e4);
415
+ const field = (k) => new RegExp(`^${k}="?([^"\\n]*)"?$`, "m").exec(text)?.[1];
416
+ return { name: field("NAME"), version: field("VERSION_ID") };
417
+ } catch {
418
+ return {};
419
+ }
420
+ }
421
+ function macosVersion(release2) {
422
+ const major = Number(release2.split(".")[0]);
423
+ if (!Number.isFinite(major)) return void 0;
424
+ if (major >= 25) return String(major + 1);
425
+ if (major >= 20) return String(major - 9);
426
+ return major >= 5 ? `10.${major - 4}` : void 0;
427
+ }
428
+ var staticContexts = null;
429
+ function processContexts() {
430
+ if (!staticContexts) {
431
+ const release2 = safe(() => os.release(), "");
432
+ const platform = process.platform;
433
+ const linux = platform === "linux" ? linuxRelease() : {};
434
+ const osName = platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : platform === "linux" ? linux.name ?? "Linux" : safe(() => os.type(), platform);
435
+ const osVersion = platform === "darwin" ? macosVersion(release2) : platform === "linux" ? linux.version : release2;
436
+ const cpus2 = safe(() => os.cpus(), []);
437
+ const intl = safe(() => Intl.DateTimeFormat().resolvedOptions(), void 0);
438
+ staticContexts = {
439
+ runtime: { name: "node", version: process.version },
440
+ os: { name: osName, ...osVersion ? { version: osVersion } : {}, ...release2 ? { kernel_version: release2 } : {} },
441
+ device: {
442
+ arch: process.arch,
443
+ memory_size: safe(() => os.totalmem(), void 0),
444
+ processor_count: cpus2.length || void 0,
445
+ cpu_description: cpus2[0]?.model,
446
+ processor_frequency: cpus2[0]?.speed || void 0,
447
+ boot_time: safe(() => new Date(Date.now() - os.uptime() * 1e3).toISOString(), void 0)
448
+ },
449
+ app: { app_start_time: new Date(Date.now() - process.uptime() * 1e3).toISOString() },
450
+ culture: intl ? { locale: intl.locale, timezone: intl.timeZone } : {}
451
+ };
452
+ }
453
+ const s = staticContexts;
454
+ return {
455
+ runtime: { ...s.runtime },
456
+ os: { ...s.os },
457
+ device: { ...s.device, free_memory: safe(() => os.freemem(), void 0) },
458
+ app: { ...s.app, app_memory: safe(() => process.memoryUsage().rss, void 0) },
459
+ culture: { ...s.culture }
460
+ };
461
+ }
462
+ function safe(fn, fallback) {
463
+ try {
464
+ return fn();
465
+ } catch {
466
+ return fallback;
467
+ }
468
+ }
469
+ var bytes = (v) => Buffer.byteLength(JSON.stringify(v) ?? "", "utf8");
470
+ function trimDetail(event, max = ERROR_LIMITS.maxDetailBytes) {
471
+ let size = bytes(event);
472
+ if (size <= max) return event;
473
+ const e = { ...event };
474
+ const frames = () => (e.exception?.values ?? []).flatMap((x) => x.stacktrace?.frames ?? []);
475
+ let crumbs = e.breadcrumbs?.values ?? [];
476
+ while (crumbs.length > 0 && size > max) {
477
+ crumbs = crumbs.slice(Math.ceil(crumbs.length / 2));
478
+ e.breadcrumbs = { values: crumbs };
479
+ size = bytes(e);
480
+ }
481
+ if (size <= max) return e;
482
+ delete e.breadcrumbs;
483
+ e.exception = e.exception ? { values: e.exception.values.map((x) => ({ ...x, ...x.stacktrace ? { stacktrace: { frames: x.stacktrace.frames.map((f) => ({ ...f })) } } : {} })) } : void 0;
484
+ if (!e.exception) delete e.exception;
485
+ for (const f of frames()) {
486
+ delete f.pre_context;
487
+ delete f.post_context;
488
+ delete f.context_line;
489
+ delete f.vars;
490
+ }
491
+ size = bytes(e);
492
+ if (size <= max) return e;
493
+ e.extra = { _trimmed: true };
494
+ if (e.request) e.request = { ...e.request, headers: {} };
495
+ size = bytes(e);
496
+ if (size <= max) return e;
497
+ for (const x of e.exception?.values ?? []) if (x.stacktrace && x.stacktrace.frames.length > 50) x.stacktrace.frames = x.stacktrace.frames.slice(-50);
498
+ while (e.exception && e.exception.values.length > 1 && bytes(e) > max) e.exception.values.shift();
499
+ if (bytes(e) <= max) return e;
500
+ e.contexts = { runtime: e.contexts?.runtime ?? {} };
501
+ delete e.extra;
502
+ return bytes(e) <= max ? e : void 0;
503
+ }
504
+ function propertiesOf(event, source, release2, extra) {
505
+ const main = event.exception?.values?.[event.exception.values.length - 1];
506
+ const os3 = event.contexts?.os;
507
+ const props = {
508
+ type: (main?.type ?? "Message").slice(0, 200),
509
+ message: (main?.value ?? event.logentry?.formatted ?? "").slice(0, 1e3),
510
+ frames: toGuidelyFrames(main?.stacktrace?.frames),
511
+ source,
512
+ handled: main?.mechanism ? main.mechanism.handled !== false : event.level !== "fatal",
513
+ level: event.level,
514
+ platform: "node",
515
+ ...main?.mechanism?.type ? { mechanism: main.mechanism.type.slice(0, 60) } : {},
516
+ ...release2 ? { release: release2.slice(0, 200) } : {},
517
+ ...Array.isArray(event.fingerprint) && event.fingerprint.length ? { fingerprintParts: event.fingerprint.slice(0, 20).map((p) => String(p).slice(0, 200)) } : {},
518
+ ...event.transaction ? { transaction: String(event.transaction).slice(0, 200) } : {},
519
+ ...os3?.name ? { os: [os3.name, os3.version].filter(Boolean).join(" ").slice(0, 60) } : {}
520
+ };
521
+ const user = normalize(extra, 3);
522
+ for (const [k, v] of Object.entries(user ?? {})) if (!(k in props) && !k.startsWith("$")) props[k] = v;
523
+ return fitProperties(props);
524
+ }
525
+ function fitProperties(props) {
526
+ if (bytes(props) <= ERROR_LIMITS.maxPropertyBytes) return props;
527
+ const core = /* @__PURE__ */ new Set(["type", "message", "frames", "source", "handled", "level", "platform", "mechanism", "release", "fingerprintParts", "transaction", "os"]);
528
+ const out = {};
529
+ for (const [k, v] of Object.entries(props)) if (core.has(k)) out[k] = v;
530
+ const frames = out.frames.slice();
531
+ while (frames.length > 1 && bytes(out) > ERROR_LIMITS.maxPropertyBytes) {
532
+ frames.pop();
533
+ out.frames = frames;
534
+ }
535
+ if (bytes(out) > ERROR_LIMITS.maxPropertyBytes) out.message = String(out.message).slice(0, 200);
536
+ return out;
537
+ }
538
+
539
+ // src/errors/monitor.ts
540
+ var import_node_async_hooks = require("async_hooks");
541
+ var os2 = __toESM(require("os"), 1);
542
+ var import_node_path3 = require("path");
543
+
544
+ // src/errors/breadcrumbs.ts
545
+ var dc = __toESM(require("diagnostics_channel"), 1);
546
+ var import_node_util = require("util");
547
+ var CONSOLE_LEVEL = { debug: "debug", info: "info", log: "log", warn: "warning", error: "error" };
548
+ var wrapped = /* @__PURE__ */ new Map();
549
+ var inConsole = false;
550
+ function instrumentConsole(levels, record) {
551
+ const target = console;
552
+ const mine = [];
553
+ for (const level of new Set(levels)) {
554
+ if (typeof target[level] !== "function") continue;
555
+ let w = wrapped.get(level);
556
+ if (w && w.listeners.size === 0 && target[level] !== w.wrapper) {
557
+ wrapped.delete(level);
558
+ w = void 0;
559
+ }
560
+ if (!w) {
561
+ const original = target[level];
562
+ const listeners = /* @__PURE__ */ new Set();
563
+ const wrapper = function(...args) {
564
+ if (!inConsole && listeners.size > 0) {
565
+ inConsole = true;
566
+ try {
567
+ const message = (0, import_node_util.format)(...args);
568
+ if (!message.startsWith("[guidely-node]")) {
569
+ const crumb = { category: "console", level: CONSOLE_LEVEL[level], message: message.slice(0, 1e3), data: { arguments: normalize(args, 3), logger: "console" } };
570
+ for (const l of listeners) l({ ...crumb }, { input: args, level });
571
+ }
572
+ } catch {
573
+ } finally {
574
+ inConsole = false;
575
+ }
576
+ }
577
+ return original.apply(this, args);
578
+ };
579
+ w = { original, wrapper, listeners };
580
+ wrapped.set(level, w);
581
+ target[level] = wrapper;
582
+ }
583
+ w.listeners.add(record);
584
+ mine.push(level);
585
+ }
586
+ return () => {
587
+ for (const level of mine) {
588
+ const w = wrapped.get(level);
589
+ if (!w) continue;
590
+ w.listeners.delete(record);
591
+ if (w.listeners.size === 0 && target[level] === w.wrapper) {
592
+ target[level] = w.original;
593
+ wrapped.delete(level);
594
+ }
595
+ }
596
+ };
597
+ }
598
+ function subscribe2(name, handler) {
599
+ try {
600
+ if (typeof dc.subscribe === "function") {
601
+ dc.subscribe(name, handler);
602
+ return () => dc.unsubscribe(name, handler);
603
+ }
604
+ const ch = dc.channel(name);
605
+ ch.subscribe(handler);
606
+ return () => ch.unsubscribe(handler);
607
+ } catch {
608
+ return () => {
609
+ };
610
+ }
611
+ }
612
+ var levelOf = (status) => status === void 0 || status >= 500 ? "error" : status >= 400 ? "warning" : "info";
613
+ function instrumentHttp(record, current, ignore) {
614
+ const started = /* @__PURE__ */ new WeakMap();
615
+ const begin = (request, method, url) => {
616
+ if (ignore(url)) return;
617
+ started.set(request, { method: typeof method === "string" ? method.toUpperCase() : "GET", url: stripQuery(url), start: Date.now(), target: current() });
618
+ };
619
+ const end = (request, response, status, error) => {
620
+ const s = started.get(request);
621
+ if (!s) return;
622
+ started.delete(request);
623
+ const data = { method: s.method, url: s.url, ...status !== void 0 ? { status_code: status } : {}, duration_ms: Date.now() - s.start };
624
+ if (error) data.error = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
625
+ try {
626
+ record({ type: "http", category: "http", level: levelOf(status), data }, { request, response }, s.target);
627
+ } catch {
628
+ }
629
+ };
630
+ const statusOf2 = (response) => {
631
+ const code = response?.statusCode;
632
+ return typeof code === "number" ? code : void 0;
633
+ };
634
+ const offs = [
635
+ // http / https
636
+ subscribe2("http.client.request.start", (m) => {
637
+ const req = m.request;
638
+ if (!req || typeof req !== "object") return;
639
+ const host = typeof req.getHeader === "function" ? req.getHeader("host") : void 0;
640
+ begin(req, req.method, `${req.protocol ?? "http:"}//${typeof host === "string" ? host : req.host ?? ""}${req.path ?? "/"}`);
641
+ }),
642
+ subscribe2("http.client.response.finish", (m) => {
643
+ const { request, response } = m;
644
+ if (request) end(request, response, statusOf2(response));
645
+ }),
646
+ subscribe2("http.client.request.error", (m) => {
647
+ const { request, error } = m;
648
+ if (request) end(request, void 0, void 0, error);
649
+ }),
650
+ // fetch / undici
651
+ subscribe2("undici:request:create", (m) => {
652
+ const req = m.request;
653
+ if (!req || typeof req !== "object") return;
654
+ const origin = String(req.origin ?? "").replace(/\/+$/, "");
655
+ begin(req, req.method, `${origin}${req.path ?? "/"}`);
656
+ }),
657
+ subscribe2("undici:request:headers", (m) => {
658
+ const { request, response } = m;
659
+ if (request) end(request, response, statusOf2(response));
660
+ }),
661
+ subscribe2("undici:request:error", (m) => {
662
+ const { request, error } = m;
663
+ if (request) end(request, void 0, void 0, error);
664
+ })
665
+ ];
666
+ return () => offs.forEach((off) => off());
667
+ }
668
+
669
+ // src/errors/context-lines.ts
670
+ var import_promises = require("fs/promises");
671
+ var import_node_path2 = require("path");
672
+ var MAX_FILE_BYTES = 1e6;
673
+ var MAX_CACHED_FILES = 20;
674
+ var MAX_FRAMES = 50;
675
+ var MAX_LINE = 200;
676
+ var MAX_CONTEXT_LINES = 10;
677
+ var cache = /* @__PURE__ */ new Map();
678
+ var loading = /* @__PURE__ */ new Map();
679
+ function remember(file, lines) {
680
+ cache.delete(file);
681
+ cache.set(file, lines);
682
+ while (cache.size > MAX_CACHED_FILES) cache.delete(cache.keys().next().value);
683
+ }
684
+ function linesOf(file) {
685
+ if (cache.has(file)) {
686
+ const lines = cache.get(file);
687
+ remember(file, lines);
688
+ return Promise.resolve(lines);
689
+ }
690
+ let p = loading.get(file);
691
+ if (!p) {
692
+ p = (async () => {
693
+ try {
694
+ const s = await (0, import_promises.stat)(file);
695
+ if (!s.isFile() || s.size > MAX_FILE_BYTES) return null;
696
+ return (await (0, import_promises.readFile)(file, "utf8")).split(/\r?\n/);
697
+ } catch {
698
+ return null;
699
+ }
700
+ })().then((lines) => {
701
+ loading.delete(file);
702
+ remember(file, lines);
703
+ return lines;
704
+ });
705
+ loading.set(file, p);
706
+ }
707
+ return p;
708
+ }
709
+ function snip(line, colno) {
710
+ if (line.length <= MAX_LINE) return line;
711
+ const at = colno ? Math.max(0, colno - 1) : 0;
712
+ const start = Math.max(0, Math.min(at - 60, line.length - MAX_LINE));
713
+ const end = Math.min(line.length, start + MAX_LINE);
714
+ return `${start > 0 ? "{snip} " : ""}${line.slice(start, end)}${end < line.length ? " {snip}" : ""}`;
715
+ }
716
+ function wantsContext(frames) {
717
+ return frames.some((f) => f.in_app && f.lineno && f.abs_path && (0, import_node_path2.isAbsolute)(f.abs_path));
718
+ }
719
+ async function addContextLines(frames, n) {
720
+ const count = Math.min(Math.max(0, Math.floor(n)), MAX_CONTEXT_LINES);
721
+ if (count === 0) return;
722
+ const targets = frames.filter((f) => f.in_app && f.lineno && f.abs_path && (0, import_node_path2.isAbsolute)(f.abs_path)).slice(-MAX_FRAMES);
723
+ await Promise.all(
724
+ targets.map(async (f) => {
725
+ const lines = await linesOf(f.abs_path);
726
+ const i = f.lineno - 1;
727
+ if (!lines || i < 0 || i >= lines.length) return;
728
+ f.pre_context = lines.slice(Math.max(0, i - count), i).map((l) => snip(l));
729
+ f.context_line = snip(lines[i], f.colno);
730
+ f.post_context = lines.slice(i + 1, i + 1 + count).map((l) => snip(l));
731
+ })
732
+ );
733
+ }
734
+
735
+ // src/errors/process-handlers.ts
736
+ var import_node_util2 = require("util");
737
+ var OURS = /* @__PURE__ */ Symbol.for("guidely.processHandler");
738
+ var isOurs = (l) => typeof l === "function" && l[OURS] === true;
739
+ function nodeMode(proc) {
740
+ const args = [...proc.execArgv ?? [], ...(proc.env?.NODE_OPTIONS ?? "").split(/\s+/)];
741
+ for (const a of args) {
742
+ const m = /^--unhandled-rejections=([\w-]+)$/.exec(a);
743
+ if (m && ["throw", "strict", "warn", "warn-with-error-code", "none"].includes(m[1])) return m[1];
744
+ }
745
+ return "throw";
746
+ }
747
+ function installProcessHandlers(host, o = {}) {
748
+ const proc = o.process ?? process;
749
+ const exitOnUncaught = o.exitOnUncaught !== false;
750
+ const mode = o.unhandledRejection ?? nodeMode(proc);
751
+ const flushTimeoutMs = typeof o.flushTimeoutMs === "number" && o.flushTimeoutMs >= 0 ? o.flushTimeoutMs : 2e3;
752
+ let exiting = false;
753
+ const others = (event) => proc.listeners(event).filter((l) => !isOurs(l)).length;
754
+ const exit = (error, prefix) => {
755
+ if (exiting) return;
756
+ exiting = true;
757
+ try {
758
+ console.error(`${prefix}${(0, import_node_util2.inspect)(error)}`);
759
+ } catch {
760
+ }
761
+ let timer;
762
+ const timeout = new Promise((resolve) => {
763
+ timer = setTimeout(resolve, flushTimeoutMs);
764
+ });
765
+ void Promise.race([host.flush().catch(() => {
766
+ }), timeout]).then(() => {
767
+ clearTimeout(timer);
768
+ proc.exitCode = 1;
769
+ proc.exit(1);
770
+ });
771
+ };
772
+ const onUncaught = (error) => {
773
+ const crash = exitOnUncaught && others("uncaughtException") === 0;
774
+ host.capture(error, { mechanism: "uncaughtException", level: "fatal" });
775
+ if (crash) exit(error, "Uncaught ");
776
+ };
777
+ const onRejection = (reason) => {
778
+ const raised = mode === "strict" || mode === "throw" && others("unhandledRejection") === 0;
779
+ const crash = exitOnUncaught && raised && others("uncaughtException") === 0;
780
+ host.capture(reason, { mechanism: "unhandledRejection", level: crash ? "fatal" : "error", fallbackType: "UnhandledRejection" });
781
+ if (crash) return exit(reason, "Unhandled promise rejection: ");
782
+ if (mode === "none" || others("unhandledRejection") > 0) return;
783
+ if (mode === "warn-with-error-code") proc.exitCode = 1;
784
+ try {
785
+ console.warn(`Unhandled promise rejection: ${(0, import_node_util2.inspect)(reason)}`);
786
+ } catch {
787
+ }
788
+ };
789
+ for (const l of [onUncaught, onRejection]) l[OURS] = true;
790
+ proc.on("uncaughtException", onUncaught);
791
+ proc.on("unhandledRejection", onRejection);
792
+ let installed = true;
793
+ return () => {
794
+ if (!installed) return;
795
+ installed = false;
796
+ proc.removeListener("uncaughtException", onUncaught);
797
+ proc.removeListener("unhandledRejection", onRejection);
798
+ };
799
+ }
800
+
801
+ // src/errors/request.ts
802
+ var str = (v, max = 2e3) => typeof v === "string" && v ? v.slice(0, max) : void 0;
803
+ function nodeRequestOf(r) {
804
+ const req = r.req;
805
+ return req && typeof req === "object" ? req : r;
806
+ }
807
+ function header(headers, name) {
808
+ const v = headers?.[name];
809
+ return Array.isArray(v) ? v[0] : typeof v === "string" ? v : typeof v === "number" ? String(v) : void 0;
810
+ }
811
+ function transactionOf(input) {
812
+ if (!input || typeof input !== "object") return void 0;
813
+ const r = input;
814
+ const method = str(r.method, 16)?.toUpperCase();
815
+ const routePath = typeof r.route?.path === "string" ? r.route.path : void 0;
816
+ const route = str(r.routeOptions?.url) ?? str(r.routerPath) ?? str(r._matchedRoute) ?? (routePath !== void 0 ? `${r.baseUrl ?? ""}${routePath}` || "/" : void 0);
817
+ return method && route ? `${method} ${route}`.slice(0, 200) : void 0;
818
+ }
819
+ function requestDataOf(input, sendDefaultPii) {
820
+ if (!input || typeof input !== "object") return void 0;
821
+ const r = input;
822
+ const node = nodeRequestOf(r);
823
+ const rawHeaders = (r.headers && typeof r.headers === "object" ? r.headers : node.headers) ?? {};
824
+ const headers = {};
825
+ let n = 0;
826
+ for (const [name, value] of Object.entries(rawHeaders)) {
827
+ if (n >= 50) break;
828
+ if (value === void 0 || SENSITIVE_HEADER.test(name) || !sendDefaultPii && IP_HEADER.test(name)) continue;
829
+ headers[name.toLowerCase()] = (Array.isArray(value) ? value.join(", ") : String(value)).slice(0, 1e3);
830
+ n++;
831
+ }
832
+ const raw = str(r.originalUrl) ?? str(node.originalUrl) ?? str(r.url) ?? str(node.url) ?? "";
833
+ let url = raw;
834
+ let query = typeof r.query_string === "string" ? r.query_string : void 0;
835
+ const q = raw.indexOf("?");
836
+ if (q >= 0) {
837
+ query = raw.slice(q + 1).replace(/#.*$/, "");
838
+ url = raw.slice(0, q);
839
+ }
840
+ url = url.replace(/#.*$/, "");
841
+ if (url && !/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
842
+ const host = header(rawHeaders, "host") ?? str(r.host) ?? str(r.hostname);
843
+ const protocol = (str(r.protocol) ?? (node.socket?.encrypted ? "https" : "http")).replace(/:$/, "");
844
+ if (host) url = `${protocol}://${host}${url.startsWith("/") ? "" : "/"}${url}`;
845
+ }
846
+ const request = {
847
+ ...str(r.method, 16) ? { method: str(r.method, 16).toUpperCase() } : {},
848
+ ...url ? { url: url.slice(0, 2e3) } : {},
849
+ ...query ? { query_string: scrubQuery(query) } : {},
850
+ headers
851
+ };
852
+ if (!sendDefaultPii) return { request };
853
+ const forwarded = header(rawHeaders, "x-forwarded-for")?.split(",")[0]?.trim();
854
+ const ip = str(r.ip, 64) ?? str(forwarded, 64) ?? str(node.socket?.remoteAddress, 64);
855
+ if (ip) request.env = { REMOTE_ADDR: ip };
856
+ return { request, ...ip ? { ip } : {} };
857
+ }
858
+ function scrubRequest(request, sendDefaultPii) {
859
+ if (!request || typeof request !== "object") return void 0;
860
+ const headers = {};
861
+ for (const [k, v] of Object.entries(request.headers ?? {})) if (!SENSITIVE_HEADER.test(k) && (sendDefaultPii || !IP_HEADER.test(k))) headers[k] = String(v).slice(0, 1e3);
862
+ return {
863
+ ...request,
864
+ ...typeof request.query_string === "string" ? { query_string: scrubQuery(request.query_string) } : {},
865
+ ...typeof request.url === "string" && request.url.includes("?") ? { url: `${request.url.split("?")[0]}?${scrubQuery(request.url.split("?").slice(1).join("?"))}` } : {},
866
+ headers
867
+ };
868
+ }
869
+
870
+ // src/errors/scope.ts
871
+ var MAX_BREADCRUMBS = 100;
872
+ var MAX_TAGS = 50;
873
+ var Scope = class _Scope {
874
+ /** @internal `max`: breadcrumbs kept; `hook`: the client's breadcrumb pipeline (`beforeBreadcrumb`). */
875
+ constructor(max = MAX_BREADCRUMBS, hook) {
876
+ this.max = max;
877
+ this.hook = hook;
878
+ }
879
+ max;
880
+ hook;
881
+ tags = {};
882
+ extra = {};
883
+ contexts = {};
884
+ user = null;
885
+ level;
886
+ fingerprint;
887
+ transaction;
888
+ crumbs = [];
889
+ /** @internal The framework request this scope was opened for (read when an error is captured). */
890
+ request;
891
+ /** A tag (searchable, ≤ 50 per event; keys ≤ 32 and values ≤ 200 characters). `null` removes it. */
892
+ setTag(key, value) {
893
+ if (typeof key !== "string" || !key) return this;
894
+ const k = key.slice(0, 32);
895
+ if (value === null || value === void 0) delete this.tags[k];
896
+ else if (k in this.tags || Object.keys(this.tags).length < MAX_TAGS) this.tags[k] = String(value).slice(0, 200);
897
+ return this;
898
+ }
899
+ setTags(tags) {
900
+ if (tags && typeof tags === "object") for (const [k, v] of Object.entries(tags)) this.setTag(k, v);
901
+ return this;
902
+ }
903
+ /** Arbitrary data shown with the event. `undefined` removes it. */
904
+ setExtra(key, value) {
905
+ if (typeof key !== "string" || !key) return this;
906
+ if (value === void 0) delete this.extra[key];
907
+ else this.extra[key] = value;
908
+ return this;
909
+ }
910
+ setExtras(extras) {
911
+ if (extras && typeof extras === "object") for (const [k, v] of Object.entries(extras)) this.setExtra(k, v);
912
+ return this;
913
+ }
914
+ /** A named context (`{ name: "checkout", cartSize: 3 }`); `null` removes it. */
915
+ setContext(name, context) {
916
+ if (typeof name !== "string" || !name) return this;
917
+ if (context === null || context === void 0 || typeof context !== "object") delete this.contexts[name];
918
+ else this.contexts[name] = { ...context };
919
+ return this;
920
+ }
921
+ /** The user errors are attributed to; `user.id` becomes the event's `userId`. `null` clears it. */
922
+ setUser(user) {
923
+ this.user = user && typeof user === "object" ? { ...user } : null;
924
+ return this;
925
+ }
926
+ /** Overrides the level of events captured in this scope. */
927
+ setLevel(level) {
928
+ this.level = level;
929
+ return this;
930
+ }
931
+ setFingerprint(fingerprint) {
932
+ this.fingerprint = Array.isArray(fingerprint) ? fingerprint.map(String) : void 0;
933
+ return this;
934
+ }
935
+ /** The transaction name (`GET /users/:id`); the request middlewares set it from the matched route. */
936
+ setTransactionName(name) {
937
+ this.transaction = typeof name === "string" && name ? name.slice(0, 200) : void 0;
938
+ return this;
939
+ }
940
+ /** Records a breadcrumb through the client's `beforeBreadcrumb`. */
941
+ addBreadcrumb(crumb) {
942
+ if (this.hook) this.hook(crumb, this);
943
+ else this.pushBreadcrumb(crumb);
944
+ return this;
945
+ }
946
+ /** @internal Adds an already-filtered breadcrumb, dropping the oldest beyond the limit. */
947
+ pushBreadcrumb(crumb) {
948
+ this.crumbs.push(crumb);
949
+ if (this.crumbs.length > this.max) this.crumbs.splice(0, this.crumbs.length - this.max);
950
+ }
951
+ clearBreadcrumbs() {
952
+ this.crumbs = [];
953
+ return this;
954
+ }
955
+ /** Removes everything (tags, extras, contexts, user, breadcrumbs…). */
956
+ clear() {
957
+ this.tags = {};
958
+ this.extra = {};
959
+ this.contexts = {};
960
+ this.user = null;
961
+ this.level = void 0;
962
+ this.fingerprint = void 0;
963
+ this.transaction = void 0;
964
+ this.crumbs = [];
965
+ this.request = void 0;
966
+ return this;
967
+ }
968
+ /** A copy whose changes do not affect this scope. */
969
+ clone() {
970
+ const s = new _Scope(this.max, this.hook);
971
+ s.tags = { ...this.tags };
972
+ s.extra = { ...this.extra };
973
+ s.contexts = { ...this.contexts };
974
+ s.user = this.user ? { ...this.user } : null;
975
+ s.level = this.level;
976
+ s.fingerprint = this.fingerprint;
977
+ s.transaction = this.transaction;
978
+ s.crumbs = this.crumbs.slice();
979
+ s.request = this.request;
980
+ return s;
981
+ }
982
+ /** @internal The scope's data at capture time. */
983
+ data() {
984
+ return {
985
+ tags: { ...this.tags },
986
+ extra: { ...this.extra },
987
+ contexts: { ...this.contexts },
988
+ user: this.user ? { ...this.user } : null,
989
+ level: this.level,
990
+ fingerprint: this.fingerprint,
991
+ transaction: this.transaction,
992
+ breadcrumbs: this.crumbs.slice(),
993
+ request: this.request
994
+ };
995
+ }
996
+ };
997
+
998
+ // src/errors/monitor.ts
999
+ var LEVELS = ["fatal", "error", "warning", "log", "info", "debug"];
1000
+ var levelOf2 = (v) => typeof v === "string" && LEVELS.includes(v) ? v : v === "warn" ? "warning" : void 0;
1001
+ var KNOWN_CONTEXT_KEYS = /* @__PURE__ */ new Set(["userId", "anonymousId", "accountId", "tags", "extra", "level", "fingerprint", "contexts", "request", "user", "mechanism", "transaction"]);
1002
+ var idString = (v) => typeof v === "string" && v.trim() ? v.slice(0, 256) : typeof v === "number" && Number.isSafeInteger(v) ? String(v) : void 0;
1003
+ function statusOf(error) {
1004
+ if (!error || typeof error !== "object") return void 0;
1005
+ const e = error;
1006
+ for (const v of [e.status, e.statusCode, e.status_code, e.output?.statusCode]) {
1007
+ const n = typeof v === "string" ? Number(v) : v;
1008
+ if (typeof n === "number" && Number.isInteger(n) && n >= 100 && n < 600) return n;
1009
+ }
1010
+ return void 0;
1011
+ }
1012
+ var defaultShouldHandle = (error) => {
1013
+ const status = statusOf(error);
1014
+ return status === void 0 || status >= 500;
1015
+ };
1016
+ var ErrorMonitor = class {
1017
+ constructor(host, options) {
1018
+ this.host = host;
1019
+ const o = options && typeof options === "object" ? options : {};
1020
+ this.options = o;
1021
+ const max = typeof o.maxBreadcrumbs === "number" && o.maxBreadcrumbs >= 0 ? Math.min(MAX_BREADCRUMBS, Math.floor(o.maxBreadcrumbs)) : MAX_BREADCRUMBS;
1022
+ this.global = new Scope(max, (crumb, scope) => this.record(crumb, void 0, scope));
1023
+ const entry = typeof process !== "undefined" ? process.argv?.[1] : void 0;
1024
+ this.frameOptions = { root: entry ? (0, import_node_path3.dirname)(entry) : safeCwd(), inAppExclude: Array.isArray(o.inAppExclude) ? o.inAppExclude : void 0 };
1025
+ this.sampleRate = typeof o.sampleRate === "number" && Number.isFinite(o.sampleRate) ? Math.min(1, Math.max(0, o.sampleRate)) : 1;
1026
+ this.contextLines = typeof o.contextLines === "number" && Number.isFinite(o.contextLines) ? Math.max(0, Math.floor(o.contextLines)) : 5;
1027
+ this.pii = o.sendDefaultPii === true;
1028
+ this.release = (typeof o.release === "string" && o.release.trim() ? o.release.trim() : host.appVersion)?.slice(0, 200);
1029
+ let serverName;
1030
+ try {
1031
+ serverName = typeof o.serverName === "string" && o.serverName ? o.serverName : os2.hostname();
1032
+ } catch {
1033
+ serverName = void 0;
1034
+ }
1035
+ this.serverName = serverName?.slice(0, 200);
1036
+ if (options === false) return;
1037
+ const levels = o.captureConsole === false ? [] : Array.isArray(o.captureConsole) ? o.captureConsole : ["warn", "error"];
1038
+ if (levels.length) this.cleanups.push(instrumentConsole(levels, (crumb, hint) => this.record(crumb, hint, this.current())));
1039
+ if (o.captureHttp !== false) {
1040
+ const own = originOf(host.apiUrl);
1041
+ this.cleanups.push(
1042
+ instrumentHttp(
1043
+ (crumb, hint, target) => this.record(crumb, hint, target instanceof Scope ? target : this.global),
1044
+ () => this.current(),
1045
+ (url) => !!own && originOf(url) === own
1046
+ )
1047
+ );
1048
+ }
1049
+ }
1050
+ host;
1051
+ global;
1052
+ als = new import_node_async_hooks.AsyncLocalStorage();
1053
+ requestScopes = /* @__PURE__ */ new WeakMap();
1054
+ /** Errors already reported by an automatic hook (an error passing through two middlewares, or a middleware and the process handler). */
1055
+ captured = /* @__PURE__ */ new WeakSet();
1056
+ bound = /* @__PURE__ */ new WeakSet();
1057
+ pending = /* @__PURE__ */ new Set();
1058
+ cleanups = [];
1059
+ options;
1060
+ frameOptions;
1061
+ sampleRate;
1062
+ contextLines;
1063
+ pii;
1064
+ release;
1065
+ serverName;
1066
+ // --- scopes --------------------------------------------------------------------------------
1067
+ /** The scope of the running request / `withScope()` callback, else the process-wide one. */
1068
+ current() {
1069
+ return this.als.getStore() ?? this.global;
1070
+ }
1071
+ withScope(fn) {
1072
+ const scope = this.current().clone();
1073
+ return this.als.run(scope, () => fn(scope));
1074
+ }
1075
+ addBreadcrumb(crumb, hint) {
1076
+ this.record(crumb, hint, this.current());
1077
+ }
1078
+ /** Validates a breadcrumb, runs `beforeBreadcrumb` and adds it to `scope`. */
1079
+ record(crumb, hint, scope) {
1080
+ try {
1081
+ if (!crumb || typeof crumb !== "object") return;
1082
+ let c = {
1083
+ timestamp: typeof crumb.timestamp === "number" && Number.isFinite(crumb.timestamp) ? crumb.timestamp : Date.now() / 1e3,
1084
+ ...typeof crumb.type === "string" && crumb.type ? { type: crumb.type.slice(0, 40) } : {},
1085
+ ...typeof crumb.category === "string" && crumb.category ? { category: crumb.category.slice(0, 100) } : {},
1086
+ ...levelOf2(crumb.level) ? { level: levelOf2(crumb.level) } : {},
1087
+ ...typeof crumb.message === "string" ? { message: crumb.message.slice(0, 1e3) } : crumb.message !== void 0 ? { message: String(crumb.message).slice(0, 1e3) } : {}
1088
+ };
1089
+ const data = normalizeObject(crumb.data, 3);
1090
+ if (data) c.data = data;
1091
+ const hook = this.options.beforeBreadcrumb;
1092
+ if (typeof hook === "function") {
1093
+ let out = c;
1094
+ try {
1095
+ out = hook(c, hint);
1096
+ } catch {
1097
+ out = c;
1098
+ }
1099
+ if (out === null) return;
1100
+ if (out && typeof out === "object" && out !== c) c = { ...out, ...out.data ? { data: normalizeObject(out.data, 3) } : {} };
1101
+ }
1102
+ scope.pushBreadcrumb(c);
1103
+ } catch {
1104
+ }
1105
+ }
1106
+ // --- capture -------------------------------------------------------------------------------
1107
+ captureException(error, context) {
1108
+ const m = context?.mechanism;
1109
+ return this.capture({ error }, context, { type: m?.type ? String(m.type).slice(0, 60) : "generic", handled: m?.handled ?? true, level: "error", source: "manual" });
1110
+ }
1111
+ captureMessage(message, level, context) {
1112
+ const ctx = level && typeof level === "object" ? level : context;
1113
+ const lvl = typeof level === "string" ? levelOf2(level) : void 0;
1114
+ return this.capture({ message: typeof message === "string" ? message : String(message) }, ctx, { type: "generic", handled: true, level: lvl ?? "info", source: "manual" });
1115
+ }
1116
+ /** An error caught by an automatic hook (process handler, middleware); skipped when already reported. */
1117
+ captureAuto(error, kind) {
1118
+ if (error && typeof error === "object") {
1119
+ if (this.captured.has(error)) return void 0;
1120
+ }
1121
+ return this.capture({ error }, kind.request ? { request: kind.request } : void 0, {
1122
+ type: kind.mechanism,
1123
+ handled: false,
1124
+ level: kind.level ?? "error",
1125
+ source: kind.mechanism,
1126
+ fallbackType: kind.fallbackType
1127
+ });
1128
+ }
1129
+ capture(input, context, mech) {
1130
+ const eventId = newEventId();
1131
+ try {
1132
+ if (!this.host.accepting()) {
1133
+ this.host.report(`${"error" in input ? "captureException" : "captureMessage"}() was called after shutdown(); the event was dropped`, void 0, "closed");
1134
+ return eventId;
1135
+ }
1136
+ const ctx = context && typeof context === "object" && !Array.isArray(context) ? context : {};
1137
+ const scope = (ctx.request && typeof ctx.request === "object" ? this.requestScopes.get(ctx.request) : void 0) ?? this.current();
1138
+ const data = scope.data();
1139
+ const values = "error" in input ? exceptionChain(input.error, { type: mech.type, handled: mech.handled }, this.frameOptions, mech.fallbackType) : void 0;
1140
+ const main = values?.[values.length - 1];
1141
+ if (this.ignored(main?.type, main ? main.value : input.message)) return eventId;
1142
+ if (this.sampleRate < 1 && Math.random() >= this.sampleRate) return eventId;
1143
+ if ("error" in input && input.error && typeof input.error === "object") this.captured.add(input.error);
1144
+ const requestSource = ctx.request ?? data.request;
1145
+ const req = requestSource ? requestDataOf(requestSource, this.pii) : void 0;
1146
+ const user = { ...data.user ?? {}, ...ctx.user && typeof ctx.user === "object" ? ctx.user : {} };
1147
+ if (idString(ctx.userId)) user.id = idString(ctx.userId);
1148
+ if (req?.ip && !user.ip_address) user.ip_address = req.ip;
1149
+ const tags = new Scope().setTags(data.tags).setTags(ctx.tags ?? {}).data().tags;
1150
+ const extra = { ...data.extra, ...ctx.extra && typeof ctx.extra === "object" ? ctx.extra : {} };
1151
+ const contexts = { ...processContexts(), ...data.contexts };
1152
+ for (const [k, v] of Object.entries(ctx.contexts ?? {})) {
1153
+ if (v && typeof v === "object") contexts[k] = v;
1154
+ else delete contexts[k];
1155
+ }
1156
+ const fingerprint = Array.isArray(ctx.fingerprint) ? ctx.fingerprint.map(String) : data.fingerprint;
1157
+ const transaction = (typeof ctx.transaction === "string" && ctx.transaction ? ctx.transaction.slice(0, 200) : void 0) ?? data.transaction ?? transactionOf(requestSource);
1158
+ const level = levelOf2(ctx.level) ?? data.level ?? mech.level;
1159
+ const event = {
1160
+ event_id: eventId,
1161
+ timestamp: Date.now() / 1e3,
1162
+ platform: "node",
1163
+ level,
1164
+ ...this.release ? { release: this.release } : {},
1165
+ environment: this.host.environment,
1166
+ ...this.serverName ? { server_name: this.serverName } : {},
1167
+ ...transaction ? { transaction } : {},
1168
+ ..."message" in input ? { logentry: { formatted: input.message.slice(0, 8e3) } } : {},
1169
+ ...values ? { exception: { values } } : {},
1170
+ ...data.breadcrumbs.length ? { breadcrumbs: { values: data.breadcrumbs } } : {},
1171
+ contexts,
1172
+ ...Object.keys(tags).length ? { tags } : {},
1173
+ extra,
1174
+ user,
1175
+ ...fingerprint?.length ? { fingerprint } : {},
1176
+ ...req ? { request: req.request } : {},
1177
+ sdk: { ...this.host.sdk }
1178
+ };
1179
+ this.scrub(event);
1180
+ const properties = {};
1181
+ for (const [k, v] of Object.entries(ctx)) if (!KNOWN_CONTEXT_KEYS.has(k)) properties[k] = v;
1182
+ const ids = { anonymousId: idString(ctx.anonymousId), accountId: idString(ctx.accountId) };
1183
+ const hint = { originalException: "error" in input ? input.error : input.message, event_id: eventId };
1184
+ const finish = () => this.finish(event, hint, mech.source, properties, ids);
1185
+ const frames = values?.flatMap((v) => v.stacktrace?.frames ?? []) ?? [];
1186
+ if (this.contextLines > 0 && wantsContext(frames)) this.track(addContextLines(frames, this.contextLines).then(finish, finish));
1187
+ else {
1188
+ const done = finish();
1189
+ if (done) this.track(done);
1190
+ }
1191
+ } catch (e) {
1192
+ this.host.report(`${"error" in input ? "captureException" : "captureMessage"}() failed: ${e instanceof Error ? e.message : String(e)}`, e);
1193
+ }
1194
+ return eventId;
1195
+ }
1196
+ ignored(type2, message) {
1197
+ const patterns = this.options.ignoreErrors;
1198
+ if (!Array.isArray(patterns) || patterns.length === 0) return false;
1199
+ const candidates = [type2 && message !== void 0 ? `${type2}: ${message}` : void 0, message].filter((s) => typeof s === "string");
1200
+ return patterns.some(
1201
+ (p) => candidates.some((s) => {
1202
+ if (typeof p === "string") return !!p && s.includes(p);
1203
+ if (!(p instanceof RegExp)) return false;
1204
+ p.lastIndex = 0;
1205
+ return p.test(s);
1206
+ })
1207
+ );
1208
+ }
1209
+ /** Makes user-supplied parts of an event JSON-safe and credential-free (again after `beforeSend`). */
1210
+ scrub(event) {
1211
+ event.extra = normalizeObject(event.extra, 4) ?? {};
1212
+ const contexts = {};
1213
+ for (const [k, v] of Object.entries(event.contexts ?? {}).slice(0, 50)) {
1214
+ const c = normalizeObject(v, 4);
1215
+ if (c) contexts[k] = c;
1216
+ }
1217
+ event.contexts = contexts;
1218
+ const user = normalizeObject(event.user, 3);
1219
+ if (user && !this.pii && user.ip_address === "{{auto}}") delete user.ip_address;
1220
+ if (user && Object.keys(user).length) event.user = user;
1221
+ else delete event.user;
1222
+ if (!Object.keys(event.extra).length) delete event.extra;
1223
+ if (event.tags) {
1224
+ const tags = {};
1225
+ for (const [k, v] of Object.entries(event.tags).slice(0, MAX_TAGS)) if (v !== null && v !== void 0) tags[String(k).slice(0, 32)] = String(v).slice(0, 200);
1226
+ event.tags = tags;
1227
+ }
1228
+ if (event.request) event.request = scrubRequest(event.request, this.pii);
1229
+ if (event.breadcrumbs?.values) event.breadcrumbs = { values: event.breadcrumbs.values.slice(-MAX_BREADCRUMBS).map((b) => b.data ? { ...b, data: normalizeObject(b.data, 3) } : b) };
1230
+ }
1231
+ /** `beforeSend`, then the message: properties ≤ 8 KB, detail ≤ 64 KB. May return a promise (async `beforeSend`). */
1232
+ finish(event, hint, source, properties, ids) {
1233
+ const send = (result2) => {
1234
+ if (result2 === null) return;
1235
+ const final = result2 && typeof result2 === "object" ? result2 : event;
1236
+ this.scrub(final);
1237
+ this.send(final, source, properties, ids);
1238
+ };
1239
+ const hook = this.options.beforeSend;
1240
+ if (typeof hook !== "function") return this.send(event, source, properties, ids);
1241
+ let result;
1242
+ try {
1243
+ result = hook(event, hint);
1244
+ } catch {
1245
+ return send(event);
1246
+ }
1247
+ if (result && typeof result.then === "function") return result.then(send, () => send(event));
1248
+ return send(result);
1249
+ }
1250
+ send(event, source, extraProperties, ids) {
1251
+ try {
1252
+ const properties = propertiesOf(event, source, this.release, extraProperties);
1253
+ const userId = idString(event.user?.id);
1254
+ const timestamp = new Date(typeof event.timestamp === "number" && Number.isFinite(event.timestamp) ? event.timestamp * 1e3 : Date.now()).toISOString();
1255
+ let safe2;
1256
+ try {
1257
+ safe2 = JSON.parse(JSON.stringify(event));
1258
+ } catch {
1259
+ safe2 = normalize(event, 12);
1260
+ }
1261
+ const detail = trimDetail(safe2);
1262
+ const message = {
1263
+ type: "exception",
1264
+ messageId: event.event_id,
1265
+ timestamp,
1266
+ ...userId ? { userId } : {},
1267
+ ...ids.anonymousId ? { anonymousId: ids.anonymousId } : {},
1268
+ ...ids.accountId ? { accountId: ids.accountId } : {},
1269
+ ...this.release ? { appVersion: this.release } : {},
1270
+ properties,
1271
+ ...detail ? { detail } : {}
1272
+ };
1273
+ this.host.push(message);
1274
+ } catch (e) {
1275
+ this.host.report(`Could not build the exception event: ${e instanceof Error ? e.message : String(e)}`, e);
1276
+ }
1277
+ }
1278
+ track(p) {
1279
+ const tracked = p.catch((e) => this.host.report(`Could not build the exception event: ${e instanceof Error ? e.message : String(e)}`, e)).finally(() => {
1280
+ this.pending.delete(tracked);
1281
+ });
1282
+ this.pending.add(tracked);
1283
+ }
1284
+ /** Resolves once every event being prepared (source lines, async `beforeSend`) is queued; `null` when none is. */
1285
+ idle() {
1286
+ if (this.pending.size === 0) return null;
1287
+ return Promise.all([...this.pending]).then(() => this.idle() ?? void 0);
1288
+ }
1289
+ // --- process and framework hooks ----------------------------------------------------------
1290
+ installProcessHandlers(options) {
1291
+ try {
1292
+ const off = installProcessHandlers(
1293
+ {
1294
+ capture: (error, kind) => void this.captureAuto(error, { mechanism: kind.mechanism, level: kind.level, fallbackType: kind.fallbackType }),
1295
+ flush: () => this.host.flush()
1296
+ },
1297
+ options
1298
+ );
1299
+ this.cleanups.push(off);
1300
+ return off;
1301
+ } catch (e) {
1302
+ this.host.report(`installProcessHandlers() failed: ${e instanceof Error ? e.message : String(e)}`, e);
1303
+ return () => {
1304
+ };
1305
+ }
1306
+ }
1307
+ /** Runs `fn` in a new scope for `request`, with the request's events bound to it (body parsing, `finish`). */
1308
+ runRequest(request, emitters, fn) {
1309
+ if (!request || typeof request !== "object") return fn();
1310
+ const scope = this.current().clone();
1311
+ scope.request = request;
1312
+ scope.clearBreadcrumbs();
1313
+ this.requestScopes.set(request, scope);
1314
+ return this.als.run(scope, () => {
1315
+ for (const e of emitters) this.bindEmitter(e);
1316
+ return fn();
1317
+ });
1318
+ }
1319
+ /**
1320
+ * Listeners of a request's / response's events (`data`, `end`, `finish`) run in the async context
1321
+ * of the server's socket, not of the middleware: bind `emit` to the request scope, as domains did.
1322
+ */
1323
+ bindEmitter(emitter) {
1324
+ if (!emitter || typeof emitter !== "object" || this.bound.has(emitter)) return;
1325
+ const e = emitter;
1326
+ if (typeof e.emit !== "function") return;
1327
+ try {
1328
+ e.emit = import_node_async_hooks.AsyncResource.bind(e.emit, "GuidelyRequest", emitter);
1329
+ this.bound.add(emitter);
1330
+ } catch {
1331
+ }
1332
+ }
1333
+ captureRequestError(error, request, mechanism, options) {
1334
+ try {
1335
+ const should = typeof options?.shouldHandleError === "function" ? options.shouldHandleError : defaultShouldHandle;
1336
+ if (should(error)) this.captureAuto(error, { mechanism, request });
1337
+ } catch {
1338
+ }
1339
+ }
1340
+ expressRequestHandler() {
1341
+ return (req, res, next) => this.runRequest(req, [req, res], () => next());
1342
+ }
1343
+ expressErrorHandler(options) {
1344
+ return (error, req, _res, next) => {
1345
+ this.captureRequestError(error, req, "express", options);
1346
+ next(error);
1347
+ };
1348
+ }
1349
+ fastifyErrorHook(options) {
1350
+ return (request, _reply, error, done) => {
1351
+ this.captureRequestError(error, request, "fastify", options);
1352
+ done?.();
1353
+ };
1354
+ }
1355
+ fastifyPlugin(options) {
1356
+ const plugin = (fastify, _opts, done) => {
1357
+ fastify.addHook("onRequest", (request, reply, next) => this.runRequest(request, [request?.raw, reply?.raw], () => next()));
1358
+ fastify.addHook("preHandler", (request, _reply, next) => {
1359
+ const scope = request && typeof request === "object" ? this.requestScopes.get(request) : void 0;
1360
+ if (scope) this.als.run(scope, () => next());
1361
+ else next();
1362
+ });
1363
+ fastify.addHook("onError", this.fastifyErrorHook(options));
1364
+ done?.();
1365
+ };
1366
+ Object.assign(plugin, { [/* @__PURE__ */ Symbol.for("skip-override")]: true, [/* @__PURE__ */ Symbol.for("fastify.display-name")]: "@guidelytech/node" });
1367
+ return plugin;
1368
+ }
1369
+ koaMiddleware(options) {
1370
+ return (ctx, next) => this.runRequest(ctx, [ctx?.req, ctx?.res], async () => {
1371
+ try {
1372
+ return await next();
1373
+ } catch (error) {
1374
+ this.captureRequestError(error, ctx, "koa", options);
1375
+ throw error;
1376
+ }
1377
+ });
1378
+ }
1379
+ /** Stops the automatic breadcrumbs and process handlers. */
1380
+ close() {
1381
+ for (const off of this.cleanups.splice(0)) {
1382
+ try {
1383
+ off();
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ }
1388
+ };
1389
+ function originOf(url) {
1390
+ try {
1391
+ return new URL(url).origin;
1392
+ } catch {
1393
+ return void 0;
1394
+ }
1395
+ }
1396
+ function safeCwd() {
1397
+ try {
1398
+ return process.cwd();
1399
+ } catch {
1400
+ return void 0;
1401
+ }
1402
+ }
1403
+
148
1404
  // src/client.ts
149
1405
  var SDK_NAME = "guidely-node";
150
- var SDK_VERSION = "0.15.0";
1406
+ var SDK_VERSION = "0.17.0";
151
1407
  var DEFAULT_API_URL = "https://guidelytech.com";
152
1408
  var BATCH_PATH = "/api/v1/sdk/server/batch";
153
- var ENVIRONMENTS = ["development", "staging", "production"];
1409
+ var ENV_KEY = /^[a-z][a-z0-9-]{0,29}$/;
154
1410
  var GuidelyError = class extends Error {
155
1411
  constructor(code, message, count = 1, status, options) {
156
1412
  super(message, options);
@@ -175,6 +1431,7 @@ var Guidely = class {
175
1431
  timeoutMs;
176
1432
  onError;
177
1433
  fetch;
1434
+ appVersion;
178
1435
  sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });
179
1436
  queue = [];
180
1437
  droppedSinceReport = 0;
@@ -189,14 +1446,17 @@ var Guidely = class {
189
1446
  shutdownPromise = null;
190
1447
  exitDrain = null;
191
1448
  exitListener = null;
1449
+ /** Set once `shutdown()` has finished: events still being prepared then are dropped, not sent. */
1450
+ finished = false;
1451
+ monitor;
192
1452
  constructor(options) {
193
1453
  const o = options ?? {};
194
1454
  const key = typeof o.secretKey === "string" ? o.secretKey.trim() : "";
195
1455
  if (!key.startsWith("gly_sk_")) {
196
1456
  throw new TypeError("Guidely: secretKey must be a secret API key (gly_sk_\u2026) with the events:write scope");
197
1457
  }
198
- if (!ENVIRONMENTS.includes(o.environment)) {
199
- throw new TypeError('Guidely: environment must be "development", "staging" or "production"');
1458
+ if (typeof o.environment !== "string" || !ENV_KEY.test(o.environment)) {
1459
+ throw new TypeError('Guidely: environment must be an environment key: "development", "staging", "production" or one you added (e.g. "qa"), as shown in Settings \u2192 Environments');
200
1460
  }
201
1461
  const fetchImpl = o.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0);
202
1462
  if (!fetchImpl) throw new TypeError("Guidely: no global fetch (Node 18+ is required), pass the fetch option");
@@ -216,6 +1476,20 @@ var Guidely = class {
216
1476
  this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 1e4, 0);
217
1477
  this.timeoutMs = int(o.timeoutMs, 1e4, 1);
218
1478
  this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));
1479
+ this.appVersion = typeof o.appVersion === "string" && o.appVersion.trim() ? o.appVersion.trim() : void 0;
1480
+ this.monitor = new ErrorMonitor(
1481
+ {
1482
+ environment: o.environment,
1483
+ appVersion: this.appVersion,
1484
+ apiUrl,
1485
+ sdk: { name: SDK_NAME, version: SDK_VERSION },
1486
+ push: (message) => this.enqueueException(message),
1487
+ accepting: () => !this.closed,
1488
+ report: (message, cause, code = "invalid_message") => this.report(new GuidelyError(code, message, 1, void 0, { cause })),
1489
+ flush: () => this.flush()
1490
+ },
1491
+ o.errors
1492
+ );
219
1493
  if (o.handleExit !== false && typeof process !== "undefined" && typeof process.on === "function") {
220
1494
  this.exitListener = () => this.onBeforeExit();
221
1495
  process.on("beforeExit", this.exitListener);
@@ -227,58 +1501,166 @@ var Guidely = class {
227
1501
  }
228
1502
  /** What a user (or anonymous visitor) did. */
229
1503
  track(message) {
230
- this.enqueue("track", message);
1504
+ const withVersion = this.appVersion && message && typeof message === "object" && (message.appVersion === void 0 || message.appVersion === null);
1505
+ this.enqueue("track", withVersion ? { ...message, appVersion: this.appVersion } : message);
231
1506
  }
232
1507
  /** Account traits, and optionally adds `userId` to the account. */
233
1508
  group(message) {
234
1509
  this.enqueue("group", message);
235
1510
  }
1511
+ // --- errors ----------------------------------------------------------------------------------
1512
+ /**
1513
+ * Reports an error as an `exception` event (grouped into an issue), with the current scope's tags,
1514
+ * extras, contexts, user and breadcrumbs. Returns the event id. Never throws.
1515
+ *
1516
+ * ```ts
1517
+ * guidely.captureException(err, { userId: "u_42", tags: { area: "billing" }, extra: { invoiceId } });
1518
+ * ```
1519
+ */
1520
+ captureException(error, context) {
1521
+ return this.monitor.captureException(error, context);
1522
+ }
1523
+ /** Reports a message (level `info` unless given), grouped by its text. Returns the event id. */
1524
+ captureMessage(message, level, context) {
1525
+ return this.monitor.captureMessage(message, level, context);
1526
+ }
1527
+ /** A tag on every later error of the current scope (`null` removes it). */
1528
+ setTag(key, value) {
1529
+ this.monitor.current().setTag(key, value);
1530
+ }
1531
+ setTags(tags) {
1532
+ this.monitor.current().setTags(tags);
1533
+ }
1534
+ setExtra(key, value) {
1535
+ this.monitor.current().setExtra(key, value);
1536
+ }
1537
+ setExtras(extras) {
1538
+ this.monitor.current().setExtras(extras);
1539
+ }
1540
+ /** A named context on every later error of the current scope (`null` removes it). */
1541
+ setContext(name, context) {
1542
+ this.monitor.current().setContext(name, context);
1543
+ }
1544
+ /** The user later errors of the current scope belong to (`user.id` becomes `userId`); `null` clears it. */
1545
+ setUser(user) {
1546
+ this.monitor.current().setUser(user);
1547
+ }
1548
+ /** Adds a breadcrumb to the current scope (the last 100 ride along with each error). */
1549
+ addBreadcrumb(breadcrumb, hint) {
1550
+ this.monitor.addBreadcrumb(breadcrumb, hint);
1551
+ }
1552
+ /**
1553
+ * Runs `fn` with a copy of the current scope: tags, user and breadcrumbs set inside (and in
1554
+ * everything it starts, through AsyncLocalStorage) stay there. Returns what `fn` returns.
1555
+ */
1556
+ withScope(fn) {
1557
+ return this.monitor.withScope(fn);
1558
+ }
1559
+ /**
1560
+ * Reports `uncaughtException` (fatal) and `unhandledRejection`, keeping Node's behaviour: the error
1561
+ * is printed, queued events are sent (at most `flushTimeoutMs`) and the process exits with code 1.
1562
+ * Returns a function that removes the handlers (`shutdown()` removes them too).
1563
+ */
1564
+ installProcessHandlers(options) {
1565
+ return this.monitor.installProcessHandlers(options);
1566
+ }
1567
+ /** Express: `app.use(guidely.expressRequestHandler())` first, so each request gets its own scope. */
1568
+ expressRequestHandler() {
1569
+ return this.monitor.expressRequestHandler();
1570
+ }
1571
+ /** Express: `app.use(guidely.expressErrorHandler())` after your routes, before other error handlers. */
1572
+ expressErrorHandler(options) {
1573
+ return this.monitor.expressErrorHandler(options);
1574
+ }
1575
+ /** Fastify: `await app.register(guidely.fastifyPlugin())` (request scopes + the `onError` hook). */
1576
+ fastifyPlugin(options) {
1577
+ return this.monitor.fastifyPlugin(options);
1578
+ }
1579
+ /** Fastify: `app.addHook("onError", guidely.fastifyErrorHook())`, when you do not use the plugin. */
1580
+ fastifyErrorHook(options) {
1581
+ return this.monitor.fastifyErrorHook(options);
1582
+ }
1583
+ /** Koa: `app.use(guidely.koaMiddleware())` first: a scope per request; errors are reported and rethrown. */
1584
+ koaMiddleware(options) {
1585
+ return this.monitor.koaMiddleware(options);
1586
+ }
1587
+ // --- delivery ------------------------------------------------------------------------------
236
1588
  /** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */
237
1589
  flush() {
238
- return this.startPump(true);
1590
+ const preparing = this.monitor.idle();
1591
+ return preparing ? preparing.then(() => this.startPump(true)) : this.startPump(true);
239
1592
  }
240
1593
  /**
241
- * Stops the timer and the exit hook, then flushes, waiting at most `shutdownTimeoutMs`. Later calls
242
- * are dropped (and reported). Call it before your process exits. Never rejects.
1594
+ * Stops the timer, the exit hook and the error hooks, then flushes, waiting at most
1595
+ * `shutdownTimeoutMs`. Later calls are dropped (and reported). Call it before your process exits.
1596
+ * Never rejects.
243
1597
  */
244
1598
  shutdown() {
245
1599
  if (this.shutdownPromise) return this.shutdownPromise;
246
1600
  this.closed = true;
247
1601
  this.clearTimer();
1602
+ this.monitor.close();
248
1603
  if (this.exitListener) process.removeListener("beforeExit", this.exitListener);
249
- this.shutdownPromise = this.drain(this.shutdownTimeoutMs);
1604
+ this.shutdownPromise = this.drain(this.shutdownTimeoutMs).finally(() => {
1605
+ this.finished = true;
1606
+ });
250
1607
  return this.shutdownPromise;
251
1608
  }
252
1609
  // --- queueing ------------------------------------------------------------------------------
253
- enqueue(type, args) {
1610
+ enqueue(type2, args) {
254
1611
  try {
255
1612
  if (this.closed) {
256
- this.report(new GuidelyError("closed", `${type}() was called after shutdown(); the message was dropped`));
1613
+ this.report(new GuidelyError("closed", `${type2}() was called after shutdown(); the message was dropped`));
257
1614
  return;
258
1615
  }
259
- const message = buildMessage(type, args);
1616
+ const message = buildMessage(type2, args);
260
1617
  let json;
261
1618
  try {
262
1619
  json = JSON.stringify(message);
263
1620
  } catch (e) {
264
1621
  throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);
265
1622
  }
266
- const bytes = Buffer.byteLength(json, "utf8");
267
- if (bytes > LIMITS.maxMessageBytes) {
268
- this.report(new GuidelyError("message_too_large", `${type}() message is ${bytes} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));
1623
+ const bytes2 = Buffer.byteLength(json, "utf8");
1624
+ if (bytes2 > LIMITS.maxMessageBytes) {
1625
+ this.report(new GuidelyError("message_too_large", `${type2}() message is ${bytes2} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));
269
1626
  return;
270
1627
  }
271
- if (this.queue.length >= this.maxQueueSize) {
272
- this.queue.shift();
273
- this.droppedSinceReport++;
274
- }
275
- this.queue.push({ json, bytes });
276
- if (this.queue.length >= this.flushAt) void this.startPump(false);
277
- else this.scheduleTimer();
1628
+ this.push(json, bytes2, false);
278
1629
  } catch (e) {
279
- if (e instanceof InvalidMessageError) this.report(new GuidelyError("invalid_message", `Invalid ${type}() call dropped: ${e.message}`));
280
- else this.report(new GuidelyError("invalid_message", `${type}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, void 0, { cause: e }));
1630
+ if (e instanceof InvalidMessageError) this.report(new GuidelyError("invalid_message", `Invalid ${type2}() call dropped: ${e.message}`));
1631
+ else this.report(new GuidelyError("invalid_message", `${type2}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, void 0, { cause: e }));
1632
+ }
1633
+ }
1634
+ /**
1635
+ * Queues a built `exception` message (it may finish preparing after `shutdown()` began, which
1636
+ * still sends it) and starts sending right away: errors should not wait for the flush interval.
1637
+ */
1638
+ enqueueException(message) {
1639
+ if (this.finished) {
1640
+ this.report(new GuidelyError("closed", "an exception finished preparing after shutdown(); it was dropped"));
1641
+ return;
1642
+ }
1643
+ let json = JSON.stringify(message);
1644
+ let bytes2 = Buffer.byteLength(json, "utf8");
1645
+ if (bytes2 > ERROR_LIMITS.maxMessageBytes && "detail" in message) {
1646
+ json = JSON.stringify({ ...message, detail: void 0 });
1647
+ bytes2 = Buffer.byteLength(json, "utf8");
1648
+ }
1649
+ if (bytes2 > ERROR_LIMITS.maxMessageBytes) {
1650
+ this.report(new GuidelyError("message_too_large", `exception message is ${bytes2} bytes, more than the ${ERROR_LIMITS.maxMessageBytes}-byte limit; it was dropped`));
1651
+ return;
1652
+ }
1653
+ this.push(json, bytes2, true);
1654
+ }
1655
+ push(json, bytes2, prompt) {
1656
+ if (this.queue.length >= this.maxQueueSize) {
1657
+ this.queue.shift();
1658
+ this.droppedSinceReport++;
281
1659
  }
1660
+ this.queue.push({ json, bytes: bytes2 });
1661
+ if (prompt) void this.startPump(true);
1662
+ else if (this.queue.length >= this.flushAt) void this.startPump(false);
1663
+ else this.scheduleTimer();
282
1664
  }
283
1665
  scheduleTimer() {
284
1666
  if (this.timer !== void 0 || this.closed) return;
@@ -388,7 +1770,7 @@ var Guidely = class {
388
1770
  async drain(timeoutMs) {
389
1771
  let timer;
390
1772
  const timedOut = await Promise.race([
391
- this.startPump(true).then(() => false),
1773
+ this.flush().then(() => false),
392
1774
  new Promise((resolve) => {
393
1775
  timer = setTimeout(() => resolve(true), timeoutMs);
394
1776
  })
@@ -407,7 +1789,7 @@ var Guidely = class {
407
1789
  if (left > 0) this.report(new GuidelyError("delivery_failed", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));
408
1790
  }
409
1791
  onBeforeExit() {
410
- if (this.closed || this.exitDrain || this.queue.length === 0 && !this.pumping) return;
1792
+ if (this.closed || this.exitDrain || this.queue.length === 0 && !this.pumping && !this.monitor.idle()) return;
411
1793
  this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {
412
1794
  this.exitDrain = null;
413
1795
  });
@@ -439,6 +1821,8 @@ function failureMessage(status, failure, count, attempts) {
439
1821
  Guidely,
440
1822
  GuidelyError,
441
1823
  SDK_NAME,
442
- SDK_VERSION
1824
+ SDK_VERSION,
1825
+ Scope,
1826
+ parseStack
443
1827
  });
444
1828
  //# sourceMappingURL=index.cjs.map