@kenkaiiii/error-mom 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  // src/shared.ts
2
2
  var SDK_NAME = "@kenkaiiii/error-mom";
3
- var SDK_VERSION = "0.3.0";
3
+ var SDK_VERSION = "0.3.2";
4
4
  var MAX_BREADCRUMBS = 50;
5
5
  var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
6
6
  var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
package/dist/index.cjs ADDED
@@ -0,0 +1,375 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ initErrorMom: () => initErrorMom
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/shared.ts
28
+ var SDK_NAME = "@kenkaiiii/error-mom";
29
+ var SDK_VERSION = "0.3.2";
30
+ var MAX_BREADCRUMBS = 50;
31
+ var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
32
+ var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
33
+ var EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
34
+ function wrapFunction(capture, fn, context) {
35
+ return (...args) => {
36
+ try {
37
+ const result = fn(...args);
38
+ if (result instanceof Promise) {
39
+ return result.catch((error) => {
40
+ capture(error, context);
41
+ throw error;
42
+ });
43
+ }
44
+ return result;
45
+ } catch (error) {
46
+ capture(error, context);
47
+ throw error;
48
+ }
49
+ };
50
+ }
51
+ function redactString(value) {
52
+ return value.replace(URL_CREDENTIAL, "$1[REDACTED]").replace(EMAIL, "[REDACTED_EMAIL]");
53
+ }
54
+ function sanitize(value, depth = 0) {
55
+ if (depth > 5) return "[TRUNCATED]";
56
+ if (typeof value === "string") return redactString(value).slice(0, 1e4);
57
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
58
+ if (value instanceof Error) {
59
+ return {
60
+ name: value.name,
61
+ message: redactString(value.message),
62
+ stack: value.stack ? redactString(value.stack) : void 0
63
+ };
64
+ }
65
+ if (Array.isArray(value)) return value.slice(0, 50).map((item) => sanitize(item, depth + 1));
66
+ if (typeof value === "object" && value) {
67
+ return Object.fromEntries(
68
+ Object.entries(value).slice(0, 100).map(([key, item]) => [
69
+ key,
70
+ SECRET_KEY.test(key) ? "[REDACTED]" : sanitize(item, depth + 1)
71
+ ])
72
+ );
73
+ }
74
+ return String(value).slice(0, 2e3);
75
+ }
76
+ function printable(value) {
77
+ if (typeof value === "string") return redactString(value);
78
+ try {
79
+ return JSON.stringify(sanitize(value));
80
+ } catch {
81
+ return String(value);
82
+ }
83
+ }
84
+ function toError(value) {
85
+ if (value instanceof Error) return value;
86
+ if (typeof value === "string") return new Error(value);
87
+ return new Error(printable(value));
88
+ }
89
+ function platformName() {
90
+ if (typeof navigator !== "undefined") return navigator.platform || "browser";
91
+ return typeof process !== "undefined" ? process.platform : "unknown";
92
+ }
93
+ function createEvent(value, options, breadcrumbs, runtime, captureContext = {}) {
94
+ const error = toError(value);
95
+ const now = (/* @__PURE__ */ new Date()).toISOString();
96
+ return {
97
+ eventId: crypto.randomUUID(),
98
+ timestamp: now,
99
+ level: captureContext.level ?? "error",
100
+ error: {
101
+ name: redactString(error.name || "Error").slice(0, 500),
102
+ message: redactString(error.message || "Unknown error").slice(0, 1e4),
103
+ ...error.stack ? { stack: redactString(error.stack).slice(0, 1e5) } : {}
104
+ },
105
+ environment: options.environment ?? "production",
106
+ ...options.release ? { release: options.release } : {},
107
+ platform: platformName(),
108
+ runtime,
109
+ ...typeof location !== "undefined" ? { url: redactString(location.href) } : {},
110
+ ...captureContext.culprit ? { culprit: redactString(captureContext.culprit) } : {},
111
+ ...options.installationId ? { installationId: options.installationId } : {},
112
+ breadcrumbs: breadcrumbs.slice(-MAX_BREADCRUMBS),
113
+ tags: { ...options.tags, ...captureContext.tags },
114
+ context: sanitize(captureContext.context ?? {}) ?? {}
115
+ };
116
+ }
117
+ function endpoint(server) {
118
+ return `${server.replace(/\/$/, "")}/api/v1/events`;
119
+ }
120
+ var AI_PROVIDERS = [
121
+ [/(^|\.)api\.anthropic\.com$/, "anthropic"],
122
+ [/(^|\.)api\.openai\.com$/, "openai"],
123
+ [/(^|\.)openai\.azure\.com$/, "azure-openai"],
124
+ [/(^|\.)generativelanguage\.googleapis\.com$/, "google"],
125
+ [/(^|\.)aiplatform\.googleapis\.com$/, "google-vertex"],
126
+ [/(^|\.)bedrock(-runtime)?[.\w-]*\.amazonaws\.com$/, "aws-bedrock"],
127
+ [/(^|\.)api\.mistral\.ai$/, "mistral"],
128
+ [/(^|\.)api\.groq\.com$/, "groq"],
129
+ [/(^|\.)api\.deepseek\.com$/, "deepseek"],
130
+ [/(^|\.)api\.x\.ai$/, "xai"],
131
+ [/(^|\.)openrouter\.ai$/, "openrouter"],
132
+ [/(^|\.)api\.together\.(xyz|ai)$/, "together"],
133
+ [/(^|\.)api\.fireworks\.ai$/, "fireworks"],
134
+ [/(^|\.)api\.cohere\.(ai|com)$/, "cohere"],
135
+ [/(^|\.)api\.perplexity\.ai$/, "perplexity"],
136
+ [/(^|\.)api\.replicate\.com$/, "replicate"],
137
+ [/(^|\.)api-inference\.huggingface\.co$/, "huggingface"],
138
+ [/(^|\.)router\.huggingface\.co$/, "huggingface"],
139
+ [/(^|\.)api\.elevenlabs\.io$/, "elevenlabs"],
140
+ [/(^|\.)api\.moonshot\.(ai|cn)$/, "moonshot"],
141
+ [/(^|\.)api\.kimi\.com$/, "moonshot"],
142
+ [/(^|\.)api\.z\.ai$/, "zai"],
143
+ [/(^|\.)open\.bigmodel\.cn$/, "zai"],
144
+ [/(^|\.)dashscope(-intl)?\.aliyuncs\.com$/, "qwen"],
145
+ [/(^|\.)api\.minimax(i)?\.(chat|com|io)$/, "minimax"],
146
+ [/(^|\.)api\.lingyiwanwu\.com$/, "yi"],
147
+ [/(^|\.)api\.stepfun\.com$/, "stepfun"],
148
+ [/(^|\.)api\.baichuan-ai\.com$/, "baichuan"],
149
+ [/(^|\.)api\.siliconflow\.(cn|com)$/, "siliconflow"],
150
+ [/(^|\.)api\.cerebras\.ai$/, "cerebras"],
151
+ [/(^|\.)api\.sambanova\.ai$/, "sambanova"],
152
+ [/(^|\.)api\.deepinfra\.com$/, "deepinfra"],
153
+ [/(^|\.)api\.novita\.ai$/, "novita"],
154
+ [/(^|\.)api\.hyperbolic\.xyz$/, "hyperbolic"],
155
+ [/(^|\.)api\.studio\.nebius\.(ai|com)$/, "nebius"],
156
+ [/(^|\.)models\.inference\.ai\.azure\.com$/, "github-models"],
157
+ [/(^|\.)models\.github\.ai$/, "github-models"],
158
+ [/(^|\.)ai-gateway\.vercel\.sh$/, "vercel-ai-gateway"],
159
+ [/(^|\.)gateway\.ai\.cloudflare\.com$/, "cloudflare-ai-gateway"],
160
+ [/(^|\.)api\.voyageai\.com$/, "voyage"],
161
+ [/(^|\.)api\.jina\.ai$/, "jina"],
162
+ [/(^|\.)api\.stability\.ai$/, "stability"],
163
+ [/(^|\.)(queue\.)?fal\.run$/, "fal"],
164
+ [/(^|\.)api\.assemblyai\.com$/, "assemblyai"],
165
+ [/(^|\.)api\.deepgram\.com$/, "deepgram"],
166
+ [/(^|\.)api\.lumalabs\.ai$/, "luma"],
167
+ [/(^|\.)api\.dev\.runwayml\.com$/, "runway"]
168
+ ];
169
+ function providerForUrl(url) {
170
+ try {
171
+ const hostname = new URL(url).hostname;
172
+ for (const [pattern, provider] of AI_PROVIDERS) {
173
+ if (pattern.test(hostname)) return provider;
174
+ }
175
+ } catch {
176
+ }
177
+ return void 0;
178
+ }
179
+ function describeFailedRequest(method, url, status) {
180
+ const provider = providerForUrl(url);
181
+ const relevant = status >= 500 || provider !== void 0 && status >= 400;
182
+ if (!relevant) return void 0;
183
+ const cleanUrl = redactString(url);
184
+ const error = new Error(`${method} ${cleanUrl} returned ${status}`);
185
+ if (provider) {
186
+ const label = provider.charAt(0).toUpperCase() + provider.slice(1);
187
+ error.name = `${label.replace(/-(\w)/g, (_, c) => c.toUpperCase())}ApiError`;
188
+ }
189
+ return {
190
+ error,
191
+ context: {
192
+ culprit: "fetch",
193
+ tags: {
194
+ statusCode: String(status),
195
+ method,
196
+ ...provider ? { provider } : {}
197
+ }
198
+ }
199
+ };
200
+ }
201
+
202
+ // src/index.ts
203
+ var clients = /* @__PURE__ */ new Map();
204
+ var BrowserClient = class {
205
+ options;
206
+ breadcrumbs = [];
207
+ queue = [];
208
+ flushPromise;
209
+ restore = [];
210
+ interval;
211
+ storageKey;
212
+ nativeFetch;
213
+ constructor(options) {
214
+ this.options = {
215
+ ...options,
216
+ captureConsoleErrors: options.captureConsoleErrors ?? true,
217
+ captureFailedRequests: options.captureFailedRequests ?? true,
218
+ flushIntervalMs: options.flushIntervalMs ?? 5e3,
219
+ maxQueueSize: options.maxQueueSize ?? 100
220
+ };
221
+ this.storageKey = `error-mom:${options.projectKey.slice(-12)}`;
222
+ this.restoreQueue();
223
+ this.installGlobalHandlers();
224
+ this.interval = setInterval(() => void this.flush(), this.options.flushIntervalMs);
225
+ void this.flush();
226
+ }
227
+ captureError(error, context = {}) {
228
+ const event = createEvent(error, this.options, this.breadcrumbs, "browser", context);
229
+ this.queue.push(event);
230
+ this.queue = this.queue.slice(-this.options.maxQueueSize);
231
+ this.persistQueue();
232
+ void this.flush();
233
+ return event.eventId;
234
+ }
235
+ wrap(fn, context) {
236
+ return wrapFunction((error, ctx) => this.captureError(error, ctx), fn, context);
237
+ }
238
+ addBreadcrumb(input) {
239
+ this.breadcrumbs.push({
240
+ ...input,
241
+ timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
242
+ message: redactString(input.message).slice(0, 2e3)
243
+ });
244
+ if (this.breadcrumbs.length > MAX_BREADCRUMBS) this.breadcrumbs.shift();
245
+ }
246
+ flush() {
247
+ if (this.flushPromise) return this.flushPromise;
248
+ if (this.queue.length === 0) return Promise.resolve();
249
+ this.flushPromise = this.sendBatch().finally(() => {
250
+ this.flushPromise = void 0;
251
+ });
252
+ return this.flushPromise;
253
+ }
254
+ async sendBatch() {
255
+ const batch = this.queue.slice(0, 20);
256
+ try {
257
+ const transport = this.nativeFetch ?? fetch;
258
+ const response = await transport(endpoint(this.options.server), {
259
+ method: "POST",
260
+ headers: {
261
+ "content-type": "application/json",
262
+ "x-error-mom-key": this.options.projectKey
263
+ },
264
+ body: JSON.stringify({ events: batch, sdk: { name: SDK_NAME, version: SDK_VERSION } }),
265
+ keepalive: true
266
+ });
267
+ if (!response.ok) return;
268
+ const accepted = new Set(batch.map((event) => event.eventId));
269
+ this.queue = this.queue.filter((event) => !accepted.has(event.eventId));
270
+ this.persistQueue();
271
+ } catch {
272
+ }
273
+ }
274
+ dispose() {
275
+ if (this.interval) clearInterval(this.interval);
276
+ for (const callback of this.restore.reverse()) callback();
277
+ clients.delete(this.options.projectKey);
278
+ }
279
+ installGlobalHandlers() {
280
+ if (typeof window === "undefined") return;
281
+ const onError = (event) => {
282
+ this.captureError(event.error ?? new Error(event.message), {
283
+ level: "fatal",
284
+ ...event.filename ? { culprit: `${event.filename}:${event.lineno}:${event.colno}` } : {}
285
+ });
286
+ };
287
+ const onRejection = (event) => {
288
+ this.captureError(event.reason, { culprit: "unhandledrejection" });
289
+ };
290
+ const onVisibility = () => {
291
+ if (document.visibilityState === "hidden") void this.flush();
292
+ };
293
+ window.addEventListener("error", onError);
294
+ window.addEventListener("unhandledrejection", onRejection);
295
+ document.addEventListener("visibilitychange", onVisibility);
296
+ this.restore.push(() => window.removeEventListener("error", onError));
297
+ this.restore.push(() => window.removeEventListener("unhandledrejection", onRejection));
298
+ this.restore.push(() => document.removeEventListener("visibilitychange", onVisibility));
299
+ this.captureConsole();
300
+ this.captureFetchFailures();
301
+ }
302
+ captureConsole() {
303
+ const levels = ["debug", "info", "warn", "error"];
304
+ for (const level of levels) {
305
+ const original = console[level].bind(console);
306
+ console[level] = (...args) => {
307
+ original(...args);
308
+ const message = args.map(printable).join(" ").slice(0, 2e3);
309
+ this.addBreadcrumb({
310
+ category: "console",
311
+ level: level === "warn" ? "warning" : level,
312
+ message
313
+ });
314
+ if (level === "error" && this.options.captureConsoleErrors) {
315
+ this.captureError(args[0] instanceof Error ? args[0] : new Error(message), {
316
+ culprit: "console.error"
317
+ });
318
+ }
319
+ };
320
+ this.restore.push(() => {
321
+ console[level] = original;
322
+ });
323
+ }
324
+ }
325
+ captureFetchFailures() {
326
+ if (!this.options.captureFailedRequests || typeof fetch === "undefined") return;
327
+ const original = fetch.bind(globalThis);
328
+ this.nativeFetch = original;
329
+ globalThis.fetch = async (input, init) => {
330
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
331
+ if (url.startsWith(this.options.server)) return original(input, init);
332
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
333
+ try {
334
+ const response = await original(input, init);
335
+ const failure = describeFailedRequest(method, url, response.status);
336
+ if (failure) this.captureError(failure.error, failure.context);
337
+ return response;
338
+ } catch (error) {
339
+ this.captureError(error, { culprit: "fetch", tags: { method, url: redactString(url) } });
340
+ throw error;
341
+ }
342
+ };
343
+ this.restore.push(() => {
344
+ globalThis.fetch = original;
345
+ });
346
+ }
347
+ restoreQueue() {
348
+ try {
349
+ const stored = localStorage.getItem(this.storageKey);
350
+ if (stored) this.queue = JSON.parse(stored);
351
+ } catch {
352
+ this.queue = [];
353
+ }
354
+ }
355
+ persistQueue() {
356
+ try {
357
+ localStorage.setItem(this.storageKey, JSON.stringify(this.queue));
358
+ } catch {
359
+ }
360
+ }
361
+ };
362
+ function initErrorMom(options) {
363
+ if (!options.server || !options.projectKey) {
364
+ throw new Error("Error Mom requires both server and projectKey");
365
+ }
366
+ const existing = clients.get(options.projectKey);
367
+ if (existing) return existing;
368
+ const client = new BrowserClient(options);
369
+ clients.set(options.projectKey, client);
370
+ return client;
371
+ }
372
+ // Annotate the CommonJS export names for ESM import in node:
373
+ 0 && (module.exports = {
374
+ initErrorMom
375
+ });
@@ -0,0 +1,21 @@
1
+ import { Breadcrumb } from '@kenkaiiii/error-mom-protocol';
2
+ export { Breadcrumb, ErrorEvent } from '@kenkaiiii/error-mom-protocol';
3
+ import { C as CommonOptions, a as CaptureContext } from './shared-idwux7-h.cjs';
4
+
5
+ interface BrowserOptions extends CommonOptions {
6
+ captureFailedRequests?: boolean;
7
+ flushIntervalMs?: number;
8
+ maxQueueSize?: number;
9
+ }
10
+ interface ErrorMomBrowser {
11
+ captureError(error: unknown, context?: CaptureContext): string;
12
+ wrap<A extends unknown[], R>(fn: (...args: A) => R, context?: CaptureContext): (...args: A) => R;
13
+ addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp"> & {
14
+ timestamp?: string;
15
+ }): void;
16
+ flush(): Promise<void>;
17
+ dispose(): void;
18
+ }
19
+ declare function initErrorMom(options: BrowserOptions): ErrorMomBrowser;
20
+
21
+ export { type BrowserOptions, CaptureContext, type ErrorMomBrowser, initErrorMom };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  printable,
9
9
  redactString,
10
10
  wrapFunction
11
- } from "./chunk-LFQHPPHR.js";
11
+ } from "./chunk-GHOIVBTK.js";
12
12
 
13
13
  // src/index.ts
14
14
  var clients = /* @__PURE__ */ new Map();
package/dist/node.cjs ADDED
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/node.ts
21
+ var node_exports = {};
22
+ __export(node_exports, {
23
+ initErrorMom: () => initErrorMom
24
+ });
25
+ module.exports = __toCommonJS(node_exports);
26
+ var import_node_crypto = require("crypto");
27
+ var import_node_fs = require("fs");
28
+ var import_promises = require("fs/promises");
29
+ var import_node_os = require("os");
30
+ var import_node_path = require("path");
31
+
32
+ // src/shared.ts
33
+ var SDK_NAME = "@kenkaiiii/error-mom";
34
+ var SDK_VERSION = "0.3.2";
35
+ var MAX_BREADCRUMBS = 50;
36
+ var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
37
+ var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
38
+ var EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
39
+ function wrapFunction(capture, fn, context) {
40
+ return (...args) => {
41
+ try {
42
+ const result = fn(...args);
43
+ if (result instanceof Promise) {
44
+ return result.catch((error) => {
45
+ capture(error, context);
46
+ throw error;
47
+ });
48
+ }
49
+ return result;
50
+ } catch (error) {
51
+ capture(error, context);
52
+ throw error;
53
+ }
54
+ };
55
+ }
56
+ function redactString(value) {
57
+ return value.replace(URL_CREDENTIAL, "$1[REDACTED]").replace(EMAIL, "[REDACTED_EMAIL]");
58
+ }
59
+ function sanitize(value, depth = 0) {
60
+ if (depth > 5) return "[TRUNCATED]";
61
+ if (typeof value === "string") return redactString(value).slice(0, 1e4);
62
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
63
+ if (value instanceof Error) {
64
+ return {
65
+ name: value.name,
66
+ message: redactString(value.message),
67
+ stack: value.stack ? redactString(value.stack) : void 0
68
+ };
69
+ }
70
+ if (Array.isArray(value)) return value.slice(0, 50).map((item) => sanitize(item, depth + 1));
71
+ if (typeof value === "object" && value) {
72
+ return Object.fromEntries(
73
+ Object.entries(value).slice(0, 100).map(([key, item]) => [
74
+ key,
75
+ SECRET_KEY.test(key) ? "[REDACTED]" : sanitize(item, depth + 1)
76
+ ])
77
+ );
78
+ }
79
+ return String(value).slice(0, 2e3);
80
+ }
81
+ function printable(value) {
82
+ if (typeof value === "string") return redactString(value);
83
+ try {
84
+ return JSON.stringify(sanitize(value));
85
+ } catch {
86
+ return String(value);
87
+ }
88
+ }
89
+ function toError(value) {
90
+ if (value instanceof Error) return value;
91
+ if (typeof value === "string") return new Error(value);
92
+ return new Error(printable(value));
93
+ }
94
+ function platformName() {
95
+ if (typeof navigator !== "undefined") return navigator.platform || "browser";
96
+ return typeof process !== "undefined" ? process.platform : "unknown";
97
+ }
98
+ function createEvent(value, options, breadcrumbs, runtime, captureContext = {}) {
99
+ const error = toError(value);
100
+ const now = (/* @__PURE__ */ new Date()).toISOString();
101
+ return {
102
+ eventId: crypto.randomUUID(),
103
+ timestamp: now,
104
+ level: captureContext.level ?? "error",
105
+ error: {
106
+ name: redactString(error.name || "Error").slice(0, 500),
107
+ message: redactString(error.message || "Unknown error").slice(0, 1e4),
108
+ ...error.stack ? { stack: redactString(error.stack).slice(0, 1e5) } : {}
109
+ },
110
+ environment: options.environment ?? "production",
111
+ ...options.release ? { release: options.release } : {},
112
+ platform: platformName(),
113
+ runtime,
114
+ ...typeof location !== "undefined" ? { url: redactString(location.href) } : {},
115
+ ...captureContext.culprit ? { culprit: redactString(captureContext.culprit) } : {},
116
+ ...options.installationId ? { installationId: options.installationId } : {},
117
+ breadcrumbs: breadcrumbs.slice(-MAX_BREADCRUMBS),
118
+ tags: { ...options.tags, ...captureContext.tags },
119
+ context: sanitize(captureContext.context ?? {}) ?? {}
120
+ };
121
+ }
122
+ function endpoint(server) {
123
+ return `${server.replace(/\/$/, "")}/api/v1/events`;
124
+ }
125
+ var AI_PROVIDERS = [
126
+ [/(^|\.)api\.anthropic\.com$/, "anthropic"],
127
+ [/(^|\.)api\.openai\.com$/, "openai"],
128
+ [/(^|\.)openai\.azure\.com$/, "azure-openai"],
129
+ [/(^|\.)generativelanguage\.googleapis\.com$/, "google"],
130
+ [/(^|\.)aiplatform\.googleapis\.com$/, "google-vertex"],
131
+ [/(^|\.)bedrock(-runtime)?[.\w-]*\.amazonaws\.com$/, "aws-bedrock"],
132
+ [/(^|\.)api\.mistral\.ai$/, "mistral"],
133
+ [/(^|\.)api\.groq\.com$/, "groq"],
134
+ [/(^|\.)api\.deepseek\.com$/, "deepseek"],
135
+ [/(^|\.)api\.x\.ai$/, "xai"],
136
+ [/(^|\.)openrouter\.ai$/, "openrouter"],
137
+ [/(^|\.)api\.together\.(xyz|ai)$/, "together"],
138
+ [/(^|\.)api\.fireworks\.ai$/, "fireworks"],
139
+ [/(^|\.)api\.cohere\.(ai|com)$/, "cohere"],
140
+ [/(^|\.)api\.perplexity\.ai$/, "perplexity"],
141
+ [/(^|\.)api\.replicate\.com$/, "replicate"],
142
+ [/(^|\.)api-inference\.huggingface\.co$/, "huggingface"],
143
+ [/(^|\.)router\.huggingface\.co$/, "huggingface"],
144
+ [/(^|\.)api\.elevenlabs\.io$/, "elevenlabs"],
145
+ [/(^|\.)api\.moonshot\.(ai|cn)$/, "moonshot"],
146
+ [/(^|\.)api\.kimi\.com$/, "moonshot"],
147
+ [/(^|\.)api\.z\.ai$/, "zai"],
148
+ [/(^|\.)open\.bigmodel\.cn$/, "zai"],
149
+ [/(^|\.)dashscope(-intl)?\.aliyuncs\.com$/, "qwen"],
150
+ [/(^|\.)api\.minimax(i)?\.(chat|com|io)$/, "minimax"],
151
+ [/(^|\.)api\.lingyiwanwu\.com$/, "yi"],
152
+ [/(^|\.)api\.stepfun\.com$/, "stepfun"],
153
+ [/(^|\.)api\.baichuan-ai\.com$/, "baichuan"],
154
+ [/(^|\.)api\.siliconflow\.(cn|com)$/, "siliconflow"],
155
+ [/(^|\.)api\.cerebras\.ai$/, "cerebras"],
156
+ [/(^|\.)api\.sambanova\.ai$/, "sambanova"],
157
+ [/(^|\.)api\.deepinfra\.com$/, "deepinfra"],
158
+ [/(^|\.)api\.novita\.ai$/, "novita"],
159
+ [/(^|\.)api\.hyperbolic\.xyz$/, "hyperbolic"],
160
+ [/(^|\.)api\.studio\.nebius\.(ai|com)$/, "nebius"],
161
+ [/(^|\.)models\.inference\.ai\.azure\.com$/, "github-models"],
162
+ [/(^|\.)models\.github\.ai$/, "github-models"],
163
+ [/(^|\.)ai-gateway\.vercel\.sh$/, "vercel-ai-gateway"],
164
+ [/(^|\.)gateway\.ai\.cloudflare\.com$/, "cloudflare-ai-gateway"],
165
+ [/(^|\.)api\.voyageai\.com$/, "voyage"],
166
+ [/(^|\.)api\.jina\.ai$/, "jina"],
167
+ [/(^|\.)api\.stability\.ai$/, "stability"],
168
+ [/(^|\.)(queue\.)?fal\.run$/, "fal"],
169
+ [/(^|\.)api\.assemblyai\.com$/, "assemblyai"],
170
+ [/(^|\.)api\.deepgram\.com$/, "deepgram"],
171
+ [/(^|\.)api\.lumalabs\.ai$/, "luma"],
172
+ [/(^|\.)api\.dev\.runwayml\.com$/, "runway"]
173
+ ];
174
+ function providerForUrl(url) {
175
+ try {
176
+ const hostname = new URL(url).hostname;
177
+ for (const [pattern, provider] of AI_PROVIDERS) {
178
+ if (pattern.test(hostname)) return provider;
179
+ }
180
+ } catch {
181
+ }
182
+ return void 0;
183
+ }
184
+ function describeFailedRequest(method, url, status) {
185
+ const provider = providerForUrl(url);
186
+ const relevant = status >= 500 || provider !== void 0 && status >= 400;
187
+ if (!relevant) return void 0;
188
+ const cleanUrl = redactString(url);
189
+ const error = new Error(`${method} ${cleanUrl} returned ${status}`);
190
+ if (provider) {
191
+ const label = provider.charAt(0).toUpperCase() + provider.slice(1);
192
+ error.name = `${label.replace(/-(\w)/g, (_, c) => c.toUpperCase())}ApiError`;
193
+ }
194
+ return {
195
+ error,
196
+ context: {
197
+ culprit: "fetch",
198
+ tags: {
199
+ statusCode: String(status),
200
+ method,
201
+ ...provider ? { provider } : {}
202
+ }
203
+ }
204
+ };
205
+ }
206
+
207
+ // src/node.ts
208
+ var NodeClient = class {
209
+ options;
210
+ breadcrumbs = [];
211
+ queue = [];
212
+ operation = Promise.resolve();
213
+ interval;
214
+ spoolFile;
215
+ handlers = [];
216
+ flushPromise;
217
+ nativeFetch;
218
+ constructor(options) {
219
+ this.options = {
220
+ ...options,
221
+ captureConsoleErrors: options.captureConsoleErrors ?? true,
222
+ flushIntervalMs: options.flushIntervalMs ?? 5e3,
223
+ maxQueueSize: options.maxQueueSize ?? 1e3,
224
+ spoolDirectory: options.spoolDirectory ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".error-mom", "spool")
225
+ };
226
+ this.spoolFile = (0, import_node_path.join)(this.options.spoolDirectory, `${safeFilePart(options.projectKey)}.jsonl`);
227
+ this.operation = this.restoreQueue();
228
+ this.installProcessHandlers();
229
+ this.captureConsole();
230
+ this.captureFetchFailures();
231
+ this.interval = setInterval(() => void this.flush(), this.options.flushIntervalMs);
232
+ void this.flush();
233
+ }
234
+ captureError(error, context = {}) {
235
+ const event = createEvent(
236
+ error,
237
+ this.options,
238
+ this.breadcrumbs,
239
+ `node ${process.version}`,
240
+ context
241
+ );
242
+ this.queue.push(event);
243
+ this.queue = this.queue.slice(-this.options.maxQueueSize);
244
+ this.operation = this.operation.then(async () => {
245
+ await (0, import_promises.mkdir)(this.options.spoolDirectory, { recursive: true });
246
+ await (0, import_promises.appendFile)(this.spoolFile, `${JSON.stringify(event)}
247
+ `, { mode: 384 });
248
+ }).catch(() => void 0);
249
+ void this.flush();
250
+ return event.eventId;
251
+ }
252
+ wrap(fn, context) {
253
+ return wrapFunction((error, ctx) => this.captureError(error, ctx), fn, context);
254
+ }
255
+ addBreadcrumb(input) {
256
+ this.breadcrumbs.push({ ...input, timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() });
257
+ if (this.breadcrumbs.length > MAX_BREADCRUMBS) this.breadcrumbs.shift();
258
+ }
259
+ flush() {
260
+ if (this.flushPromise) return this.flushPromise;
261
+ this.flushPromise = this.sendBatch().finally(() => {
262
+ this.flushPromise = void 0;
263
+ });
264
+ return this.flushPromise;
265
+ }
266
+ async sendBatch() {
267
+ await this.operation;
268
+ if (this.queue.length === 0) return;
269
+ const batch = this.queue.slice(0, 50);
270
+ try {
271
+ const transport = this.nativeFetch ?? fetch;
272
+ const response = await transport(endpoint(this.options.server), {
273
+ method: "POST",
274
+ headers: {
275
+ "content-type": "application/json",
276
+ "x-error-mom-key": this.options.projectKey
277
+ },
278
+ body: JSON.stringify({ events: batch, sdk: { name: SDK_NAME, version: SDK_VERSION } })
279
+ });
280
+ if (!response.ok) return;
281
+ const accepted = new Set(batch.map((event) => event.eventId));
282
+ this.queue = this.queue.filter((event) => !accepted.has(event.eventId));
283
+ this.operation = this.operation.then(() => this.rewriteSpool());
284
+ await this.operation;
285
+ } catch {
286
+ }
287
+ }
288
+ async dispose() {
289
+ if (this.interval) clearInterval(this.interval);
290
+ for (const remove of this.handlers.reverse()) remove();
291
+ await this.flush();
292
+ if (singleton === this) singleton = void 0;
293
+ }
294
+ installProcessHandlers() {
295
+ const onUncaught = (error) => {
296
+ this.captureError(error, { level: "fatal", culprit: "uncaughtException" });
297
+ };
298
+ const onRejection = (reason) => {
299
+ this.captureError(reason, { culprit: "unhandledRejection" });
300
+ };
301
+ const onExit = () => {
302
+ this.persistQueueSync();
303
+ };
304
+ process.on("uncaughtExceptionMonitor", onUncaught);
305
+ process.on("unhandledRejection", onRejection);
306
+ process.on("exit", onExit);
307
+ this.handlers.push(() => process.off("uncaughtExceptionMonitor", onUncaught));
308
+ this.handlers.push(() => process.off("unhandledRejection", onRejection));
309
+ this.handlers.push(() => process.off("exit", onExit));
310
+ }
311
+ persistQueueSync() {
312
+ if (this.queue.length === 0) return;
313
+ try {
314
+ (0, import_node_fs.mkdirSync)(this.options.spoolDirectory, { recursive: true });
315
+ const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
316
+ (0, import_node_fs.writeFileSync)(this.spoolFile, `${contents}
317
+ `, { mode: 384 });
318
+ } catch {
319
+ }
320
+ }
321
+ captureFetchFailures() {
322
+ if (this.options.captureFailedRequests === false || typeof fetch === "undefined") return;
323
+ const original = fetch.bind(globalThis);
324
+ this.nativeFetch = original;
325
+ globalThis.fetch = (async (input, init) => {
326
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
327
+ if (url.startsWith(this.options.server)) return original(input, init);
328
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
329
+ try {
330
+ const response = await original(input, init);
331
+ const failure = describeFailedRequest(method, url, response.status);
332
+ if (failure) this.captureError(failure.error, failure.context);
333
+ return response;
334
+ } catch (error) {
335
+ this.captureError(error, { culprit: "fetch", tags: { method, url: redactString(url) } });
336
+ throw error;
337
+ }
338
+ });
339
+ this.handlers.push(() => {
340
+ globalThis.fetch = original;
341
+ });
342
+ }
343
+ captureConsole() {
344
+ const levels = ["debug", "info", "warn", "error"];
345
+ for (const level of levels) {
346
+ const original = console[level].bind(console);
347
+ console[level] = (...args) => {
348
+ original(...args);
349
+ const message = args.map(printable).join(" ").slice(0, 2e3);
350
+ this.addBreadcrumb({
351
+ category: "console",
352
+ level: level === "warn" ? "warning" : level,
353
+ message
354
+ });
355
+ if (level === "error" && this.options.captureConsoleErrors) {
356
+ this.captureError(args[0] instanceof Error ? args[0] : new Error(message), {
357
+ culprit: "console.error"
358
+ });
359
+ }
360
+ };
361
+ this.handlers.push(() => {
362
+ console[level] = original;
363
+ });
364
+ }
365
+ }
366
+ async restoreQueue() {
367
+ try {
368
+ const contents = await (0, import_promises.readFile)(this.spoolFile, "utf8");
369
+ const restored = contents.split("\n").filter(Boolean).flatMap((line) => {
370
+ try {
371
+ return [JSON.parse(line)];
372
+ } catch {
373
+ return [];
374
+ }
375
+ });
376
+ const merged = new Map(
377
+ [...restored, ...this.queue].map((event) => [event.eventId, event])
378
+ );
379
+ this.queue = [...merged.values()].slice(-this.options.maxQueueSize);
380
+ } catch {
381
+ }
382
+ }
383
+ async rewriteSpool() {
384
+ await (0, import_promises.mkdir)(this.options.spoolDirectory, { recursive: true });
385
+ const temporary = `${this.spoolFile}.${process.pid}.tmp`;
386
+ const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
387
+ await (0, import_promises.writeFile)(temporary, contents ? `${contents}
388
+ ` : "", { mode: 384 });
389
+ await (0, import_promises.rename)(temporary, this.spoolFile);
390
+ }
391
+ };
392
+ function safeFilePart(projectKey) {
393
+ return (0, import_node_crypto.createHash)("sha256").update(projectKey).digest("hex").slice(0, 32);
394
+ }
395
+ var singleton;
396
+ function initErrorMom(options) {
397
+ if (!options.server || !options.projectKey) {
398
+ throw new Error("Error Mom requires both server and projectKey");
399
+ }
400
+ singleton ??= new NodeClient(options);
401
+ return singleton;
402
+ }
403
+ // Annotate the CommonJS export names for ESM import in node:
404
+ 0 && (module.exports = {
405
+ initErrorMom
406
+ });
@@ -0,0 +1,22 @@
1
+ import { Breadcrumb } from '@kenkaiiii/error-mom-protocol';
2
+ export { Breadcrumb, ErrorEvent } from '@kenkaiiii/error-mom-protocol';
3
+ import { a as CaptureContext, C as CommonOptions } from './shared-idwux7-h.cjs';
4
+
5
+ interface NodeOptions extends CommonOptions {
6
+ captureFailedRequests?: boolean;
7
+ spoolDirectory?: string;
8
+ flushIntervalMs?: number;
9
+ maxQueueSize?: number;
10
+ }
11
+ interface ErrorMomNode {
12
+ captureError(error: unknown, context?: CaptureContext): string;
13
+ wrap<A extends unknown[], R>(fn: (...args: A) => R, context?: CaptureContext): (...args: A) => R;
14
+ addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp"> & {
15
+ timestamp?: string;
16
+ }): void;
17
+ flush(): Promise<void>;
18
+ dispose(): Promise<void>;
19
+ }
20
+ declare function initErrorMom(options: NodeOptions): ErrorMomNode;
21
+
22
+ export { CaptureContext, type ErrorMomNode, type NodeOptions, initErrorMom };
package/dist/node.js CHANGED
@@ -8,10 +8,11 @@ import {
8
8
  printable,
9
9
  redactString,
10
10
  wrapFunction
11
- } from "./chunk-LFQHPPHR.js";
11
+ } from "./chunk-GHOIVBTK.js";
12
12
 
13
13
  // src/node.ts
14
14
  import { createHash } from "crypto";
15
+ import { mkdirSync, writeFileSync } from "fs";
15
16
  import { mkdir, readFile, rename, writeFile, appendFile } from "fs/promises";
16
17
  import { homedir } from "os";
17
18
  import { join } from "path";
@@ -108,10 +109,25 @@ var NodeClient = class {
108
109
  const onRejection = (reason) => {
109
110
  this.captureError(reason, { culprit: "unhandledRejection" });
110
111
  };
112
+ const onExit = () => {
113
+ this.persistQueueSync();
114
+ };
111
115
  process.on("uncaughtExceptionMonitor", onUncaught);
112
116
  process.on("unhandledRejection", onRejection);
117
+ process.on("exit", onExit);
113
118
  this.handlers.push(() => process.off("uncaughtExceptionMonitor", onUncaught));
114
119
  this.handlers.push(() => process.off("unhandledRejection", onRejection));
120
+ this.handlers.push(() => process.off("exit", onExit));
121
+ }
122
+ persistQueueSync() {
123
+ if (this.queue.length === 0) return;
124
+ try {
125
+ mkdirSync(this.options.spoolDirectory, { recursive: true });
126
+ const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
127
+ writeFileSync(this.spoolFile, `${contents}
128
+ `, { mode: 384 });
129
+ } catch {
130
+ }
115
131
  }
116
132
  captureFetchFailures() {
117
133
  if (this.options.captureFailedRequests === false || typeof fetch === "undefined") return;
@@ -0,0 +1,19 @@
1
+ import { ErrorEvent } from '@kenkaiiii/error-mom-protocol';
2
+
3
+ interface CommonOptions {
4
+ server: string;
5
+ projectKey: string;
6
+ environment?: string;
7
+ release?: string;
8
+ installationId?: string;
9
+ tags?: Record<string, string>;
10
+ captureConsoleErrors?: boolean;
11
+ }
12
+ interface CaptureContext {
13
+ level?: ErrorEvent["level"];
14
+ culprit?: string;
15
+ tags?: Record<string, string>;
16
+ context?: Record<string, unknown>;
17
+ }
18
+
19
+ export type { CommonOptions as C, CaptureContext as a };
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@kenkaiiii/error-mom",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Automatic browser and Node.js error capture for self-hosted Error Mom",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
6
+ "main": "./dist/index.cjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
12
13
  },
13
14
  "./node": {
14
15
  "types": "./dist/node.d.ts",
15
- "import": "./dist/node.js"
16
+ "import": "./dist/node.js",
17
+ "require": "./dist/node.cjs"
16
18
  }
17
19
  },
18
20
  "files": [
@@ -30,8 +32,9 @@
30
32
  "access": "public"
31
33
  },
32
34
  "license": "MIT",
35
+ "module": "./dist/index.js",
33
36
  "scripts": {
34
- "build": "tsup src/index.ts src/node.ts --format esm --dts --clean",
37
+ "build": "tsup src/index.ts src/node.ts --format esm,cjs --dts --clean",
35
38
  "check": "tsc --noEmit",
36
39
  "test": "vitest run"
37
40
  }