@errorgap/bun 0.1.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/LICENSE +21 -0
- package/README.md +77 -0
- package/package.json +36 -0
- package/src/backtrace.ts +42 -0
- package/src/client.ts +116 -0
- package/src/configuration.ts +58 -0
- package/src/filter.ts +31 -0
- package/src/handlers.ts +37 -0
- package/src/index.ts +62 -0
- package/src/notice.ts +71 -0
- package/src/version.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Errorgap
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @errorgap/bun
|
|
2
|
+
|
|
3
|
+
Bun notifier for [Errorgap](https://errorgap.com). Errors only in v1.
|
|
4
|
+
|
|
5
|
+
Requires Bun 1.0+.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add @errorgap/bun
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package ships as TypeScript source — no build step. Bun loads it
|
|
14
|
+
directly.
|
|
15
|
+
|
|
16
|
+
## Configure
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Errorgap } from "@errorgap/bun";
|
|
20
|
+
|
|
21
|
+
Errorgap.init({
|
|
22
|
+
endpoint: process.env.ERRORGAP_ENDPOINT,
|
|
23
|
+
projectSlug: process.env.ERRORGAP_PROJECT_SLUG,
|
|
24
|
+
apiKey: process.env.ERRORGAP_API_KEY,
|
|
25
|
+
environment: process.env.NODE_ENV ?? "production",
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`init` reads `ERRORGAP_*` env vars when fields are omitted. By default
|
|
30
|
+
it installs `process.on("uncaughtException")` and
|
|
31
|
+
`process.on("unhandledRejection")`; pass `captureGlobals: false` to skip.
|
|
32
|
+
|
|
33
|
+
## Manual notification
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
try {
|
|
37
|
+
await risky();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
await Errorgap.notify(err, { context: { component: "checkout" } });
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`notify` returns a `DeliveryResult` (`status`, `body`, `error`, `queued`).
|
|
45
|
+
The SDK never throws.
|
|
46
|
+
|
|
47
|
+
## Configuration reference
|
|
48
|
+
|
|
49
|
+
| Option | Default | Notes |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `endpoint` | `ERRORGAP_ENDPOINT` or `http://127.0.0.1:3030` | |
|
|
52
|
+
| `projectSlug` | `ERRORGAP_PROJECT_SLUG` | **Required** |
|
|
53
|
+
| `projectId` | `ERRORGAP_PROJECT_ID` | |
|
|
54
|
+
| `apiKey` | `ERRORGAP_API_KEY` | Sent as `x-errorgap-project-key` |
|
|
55
|
+
| `environment` | `NODE_ENV` or `"production"` | |
|
|
56
|
+
| `release` | — | |
|
|
57
|
+
| `async` | `true` | Fire-and-forget delivery |
|
|
58
|
+
| `logger` | `console` | Pass `null` to silence |
|
|
59
|
+
| `filterKeys` | `["password", "token", ...]` | Substring, case-insensitive |
|
|
60
|
+
| `captureGlobals` | `true` | Install process error hooks |
|
|
61
|
+
|
|
62
|
+
## Graceful flush
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
await Errorgap.flush();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
bun install
|
|
72
|
+
bun test
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT.
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@errorgap/bun",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bun notifier for Errorgap error tracking.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Errorgap",
|
|
7
|
+
"homepage": "https://github.com/errorgaphq/errorgap-bun",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/errorgaphq/errorgap-bun.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["errorgap", "bun", "error-tracking", "exceptions", "monitoring"],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./src/index.ts",
|
|
15
|
+
"module": "./src/index.ts",
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./src/index.ts",
|
|
20
|
+
"default": "./src/index.ts"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": ["src", "README.md", "LICENSE"],
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "^1.1.0",
|
|
34
|
+
"typescript": "^5.3.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/backtrace.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface BacktraceFrame {
|
|
2
|
+
file?: string;
|
|
3
|
+
line?: number;
|
|
4
|
+
function?: string;
|
|
5
|
+
in_app?: boolean;
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const V8_AT = /^\s*at\s+(?:(.*?)\s+\()?(.+?)(?::(\d+))?(?::(\d+))?\)?$/;
|
|
10
|
+
|
|
11
|
+
export function parseBacktrace(error: Error): BacktraceFrame[] {
|
|
12
|
+
const stack = typeof error.stack === "string" ? error.stack : "";
|
|
13
|
+
if (!stack) return [];
|
|
14
|
+
const lines = stack.split("\n");
|
|
15
|
+
const frames: BacktraceFrame[] = [];
|
|
16
|
+
let index = 0;
|
|
17
|
+
for (const raw of lines) {
|
|
18
|
+
const trimmed = raw.trim();
|
|
19
|
+
if (!trimmed.startsWith("at ")) continue;
|
|
20
|
+
const m = trimmed.match(V8_AT);
|
|
21
|
+
if (!m) continue;
|
|
22
|
+
const fnName = m[1];
|
|
23
|
+
const location = (m[2] ?? "").replace(/^file:\/\//, "");
|
|
24
|
+
const lineNumber = m[3] ? Number(m[3]) : undefined;
|
|
25
|
+
frames.push({
|
|
26
|
+
file: location,
|
|
27
|
+
line: lineNumber,
|
|
28
|
+
function: fnName || undefined,
|
|
29
|
+
in_app: isInApp(location),
|
|
30
|
+
index: index++,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return frames;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isInApp(file: string): boolean {
|
|
37
|
+
if (!file) return false;
|
|
38
|
+
if (file.includes("/node_modules/")) return false;
|
|
39
|
+
if (file.startsWith("node:")) return false;
|
|
40
|
+
if (file.startsWith("bun:")) return false; // Bun internal modules
|
|
41
|
+
return true;
|
|
42
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Configuration } from "./configuration.js";
|
|
2
|
+
import { buildNotice, type NoticeContext, type NoticePayload } from "./notice.js";
|
|
3
|
+
import { VERSION } from "./version.js";
|
|
4
|
+
|
|
5
|
+
export interface DeliveryResult {
|
|
6
|
+
status?: number;
|
|
7
|
+
body?: string;
|
|
8
|
+
error?: unknown;
|
|
9
|
+
queued?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Client {
|
|
13
|
+
private pending = new Set<Promise<unknown>>();
|
|
14
|
+
|
|
15
|
+
constructor(private configuration: Configuration) {}
|
|
16
|
+
|
|
17
|
+
configure(configuration: Configuration): void {
|
|
18
|
+
this.configuration = configuration;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async notify(
|
|
22
|
+
error: unknown,
|
|
23
|
+
options: NoticeContext & { sync?: boolean } = {},
|
|
24
|
+
): Promise<DeliveryResult> {
|
|
25
|
+
try {
|
|
26
|
+
this.configuration.validate();
|
|
27
|
+
const err = coerceError(error);
|
|
28
|
+
const notice = buildNotice(err, this.configuration, options);
|
|
29
|
+
|
|
30
|
+
if (options.sync || !this.configuration.async) {
|
|
31
|
+
const p = this.deliver(notice);
|
|
32
|
+
this.track(p);
|
|
33
|
+
return await p;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.track(this.deliver(notice));
|
|
37
|
+
return { queued: true, status: 202 };
|
|
38
|
+
} catch (exception) {
|
|
39
|
+
this.log(exception);
|
|
40
|
+
return { error: exception };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async deliver(notice: NoticePayload): Promise<DeliveryResult> {
|
|
45
|
+
const url = noticesUrl(this.configuration);
|
|
46
|
+
const headers: Record<string, string> = {
|
|
47
|
+
"content-type": "application/json",
|
|
48
|
+
"user-agent": `errorgap-bun/${VERSION}`,
|
|
49
|
+
};
|
|
50
|
+
if (this.configuration.apiKey) {
|
|
51
|
+
headers["x-errorgap-project-key"] = this.configuration.apiKey;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify(notice),
|
|
58
|
+
});
|
|
59
|
+
const body = await safeBody(response);
|
|
60
|
+
return { status: response.status, body };
|
|
61
|
+
} catch (exception) {
|
|
62
|
+
this.log(exception);
|
|
63
|
+
return { error: exception };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async flush(): Promise<void> {
|
|
68
|
+
while (this.pending.size > 0) {
|
|
69
|
+
await Promise.all(Array.from(this.pending));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private track(promise: Promise<unknown>): void {
|
|
74
|
+
const wrapped = promise.catch(() => undefined);
|
|
75
|
+
this.pending.add(wrapped);
|
|
76
|
+
void wrapped.finally(() => this.pending.delete(wrapped));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private log(exception: unknown): void {
|
|
80
|
+
const logger = this.configuration.logger;
|
|
81
|
+
if (!logger) return;
|
|
82
|
+
const message = exception instanceof Error
|
|
83
|
+
? `${exception.name}: ${exception.message}`
|
|
84
|
+
: String(exception);
|
|
85
|
+
logger.warn(`[errorgap] ${message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function noticesUrl(configuration: Configuration): string {
|
|
90
|
+
const base = configuration.endpoint.endsWith("/")
|
|
91
|
+
? configuration.endpoint.slice(0, -1)
|
|
92
|
+
: configuration.endpoint;
|
|
93
|
+
return `${base}/api/projects/${configuration.projectSlug}/notices`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function safeBody(response: Response): Promise<string> {
|
|
97
|
+
try {
|
|
98
|
+
return await response.text();
|
|
99
|
+
} catch {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function coerceError(error: unknown): Error {
|
|
105
|
+
if (error instanceof Error) return error;
|
|
106
|
+
if (typeof error === "string") return new Error(error);
|
|
107
|
+
if (error && typeof error === "object") {
|
|
108
|
+
const obj = error as { message?: unknown; name?: unknown };
|
|
109
|
+
const err = new Error(
|
|
110
|
+
typeof obj.message === "string" ? obj.message : JSON.stringify(error),
|
|
111
|
+
);
|
|
112
|
+
if (typeof obj.name === "string") err.name = obj.name;
|
|
113
|
+
return err;
|
|
114
|
+
}
|
|
115
|
+
return new Error(String(error));
|
|
116
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
warn(message: string, ...args: unknown[]): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface ConfigurationInput {
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
projectSlug?: string;
|
|
8
|
+
projectId?: string;
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
environment?: string;
|
|
11
|
+
release?: string;
|
|
12
|
+
async?: boolean;
|
|
13
|
+
logger?: Logger | null;
|
|
14
|
+
filterKeys?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_FILTER_KEYS = [
|
|
18
|
+
"password",
|
|
19
|
+
"password_confirmation",
|
|
20
|
+
"token",
|
|
21
|
+
"secret",
|
|
22
|
+
"api_key",
|
|
23
|
+
"authorization",
|
|
24
|
+
"cookie",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export class Configuration {
|
|
28
|
+
endpoint: string;
|
|
29
|
+
projectSlug: string | undefined;
|
|
30
|
+
projectId: string | undefined;
|
|
31
|
+
apiKey: string | undefined;
|
|
32
|
+
environment: string;
|
|
33
|
+
release: string | undefined;
|
|
34
|
+
async: boolean;
|
|
35
|
+
logger: Logger | null;
|
|
36
|
+
filterKeys: string[];
|
|
37
|
+
|
|
38
|
+
constructor(input: ConfigurationInput = {}) {
|
|
39
|
+
this.endpoint = input.endpoint ?? process.env.ERRORGAP_ENDPOINT ?? "http://127.0.0.1:3030";
|
|
40
|
+
this.projectSlug = input.projectSlug ?? process.env.ERRORGAP_PROJECT_SLUG;
|
|
41
|
+
this.projectId = input.projectId ?? process.env.ERRORGAP_PROJECT_ID;
|
|
42
|
+
this.apiKey = input.apiKey ?? process.env.ERRORGAP_API_KEY;
|
|
43
|
+
this.environment = input.environment ?? process.env.NODE_ENV ?? "production";
|
|
44
|
+
this.release = input.release;
|
|
45
|
+
this.async = input.async ?? true;
|
|
46
|
+
this.logger = input.logger === undefined ? console : input.logger;
|
|
47
|
+
this.filterKeys = input.filterKeys ?? [...DEFAULT_FILTER_KEYS];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
validate(): void {
|
|
51
|
+
if (!this.endpoint || this.endpoint.trim().length === 0) {
|
|
52
|
+
throw new Error("Errorgap endpoint is required");
|
|
53
|
+
}
|
|
54
|
+
if (!this.projectSlug || this.projectSlug.trim().length === 0) {
|
|
55
|
+
throw new Error("Errorgap projectSlug is required");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/filter.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const FILTERED = "[FILTERED]";
|
|
2
|
+
|
|
3
|
+
export function filterParams(
|
|
4
|
+
params: Record<string, unknown>,
|
|
5
|
+
filterKeys: string[],
|
|
6
|
+
): Record<string, unknown> {
|
|
7
|
+
const lowered = filterKeys.map((k) => k.toLowerCase());
|
|
8
|
+
return walk(params, lowered);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function walk(
|
|
12
|
+
value: Record<string, unknown>,
|
|
13
|
+
loweredKeys: string[],
|
|
14
|
+
): Record<string, unknown> {
|
|
15
|
+
const out: Record<string, unknown> = {};
|
|
16
|
+
for (const [key, val] of Object.entries(value)) {
|
|
17
|
+
if (isSensitive(key, loweredKeys)) {
|
|
18
|
+
out[key] = FILTERED;
|
|
19
|
+
} else if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
20
|
+
out[key] = walk(val as Record<string, unknown>, loweredKeys);
|
|
21
|
+
} else {
|
|
22
|
+
out[key] = val;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isSensitive(key: string, loweredKeys: string[]): boolean {
|
|
29
|
+
const k = key.toLowerCase();
|
|
30
|
+
return loweredKeys.some((needle) => k.includes(needle));
|
|
31
|
+
}
|
package/src/handlers.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Client } from "./client.js";
|
|
2
|
+
|
|
3
|
+
let installed = false;
|
|
4
|
+
let uncaughtHandler: ((err: Error) => void) | null = null;
|
|
5
|
+
let rejectionHandler: ((reason: unknown) => void) | null = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook Bun's `uncaughtException` and `unhandledRejection` events on the
|
|
9
|
+
* Node-compatible `process` global.
|
|
10
|
+
*/
|
|
11
|
+
export function installProcessHandlers(client: Client): void {
|
|
12
|
+
if (installed) return;
|
|
13
|
+
installed = true;
|
|
14
|
+
uncaughtHandler = (err: Error) => {
|
|
15
|
+
void client.notify(err, {
|
|
16
|
+
sync: true,
|
|
17
|
+
context: { source: "uncaughtException" },
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
rejectionHandler = (reason: unknown) => {
|
|
21
|
+
void client.notify(reason, {
|
|
22
|
+
sync: true,
|
|
23
|
+
context: { source: "unhandledRejection" },
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
process.on("uncaughtException", uncaughtHandler);
|
|
27
|
+
process.on("unhandledRejection", rejectionHandler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function uninstallProcessHandlers(): void {
|
|
31
|
+
if (!installed) return;
|
|
32
|
+
if (uncaughtHandler) process.off("uncaughtException", uncaughtHandler);
|
|
33
|
+
if (rejectionHandler) process.off("unhandledRejection", rejectionHandler);
|
|
34
|
+
uncaughtHandler = null;
|
|
35
|
+
rejectionHandler = null;
|
|
36
|
+
installed = false;
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Client, type DeliveryResult } from "./client.js";
|
|
2
|
+
import { Configuration, type ConfigurationInput } from "./configuration.js";
|
|
3
|
+
import { installProcessHandlers, uninstallProcessHandlers } from "./handlers.js";
|
|
4
|
+
import type { NoticeContext } from "./notice.js";
|
|
5
|
+
import { VERSION } from "./version.js";
|
|
6
|
+
|
|
7
|
+
export type { ConfigurationInput, Logger } from "./configuration.js";
|
|
8
|
+
export type { BacktraceFrame } from "./backtrace.js";
|
|
9
|
+
export type { NoticeContext, NoticePayload } from "./notice.js";
|
|
10
|
+
export type { DeliveryResult } from "./client.js";
|
|
11
|
+
export { Client } from "./client.js";
|
|
12
|
+
export { Configuration } from "./configuration.js";
|
|
13
|
+
export { VERSION };
|
|
14
|
+
|
|
15
|
+
let configuration = new Configuration();
|
|
16
|
+
let client = new Client(configuration);
|
|
17
|
+
|
|
18
|
+
export interface InitOptions extends ConfigurationInput {
|
|
19
|
+
/** Install process.uncaughtException / unhandledRejection handlers. */
|
|
20
|
+
captureGlobals?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function init(options: InitOptions = {}): void {
|
|
24
|
+
const { captureGlobals = true, ...rest } = options;
|
|
25
|
+
configuration = new Configuration(rest);
|
|
26
|
+
client.configure(configuration);
|
|
27
|
+
if (captureGlobals) {
|
|
28
|
+
installProcessHandlers(client);
|
|
29
|
+
} else {
|
|
30
|
+
uninstallProcessHandlers();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function notify(
|
|
35
|
+
error: unknown,
|
|
36
|
+
options: NoticeContext & { sync?: boolean } = {},
|
|
37
|
+
): Promise<DeliveryResult> {
|
|
38
|
+
return client.notify(error, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function flush(): Promise<void> {
|
|
42
|
+
return client.flush();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getConfiguration(): Configuration {
|
|
46
|
+
return configuration;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getClient(): Client {
|
|
50
|
+
return client;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const Errorgap = {
|
|
54
|
+
init,
|
|
55
|
+
notify,
|
|
56
|
+
flush,
|
|
57
|
+
configuration: getConfiguration,
|
|
58
|
+
client: getClient,
|
|
59
|
+
VERSION,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export { init, notify, flush };
|
package/src/notice.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Configuration } from "./configuration.js";
|
|
2
|
+
import { parseBacktrace, type BacktraceFrame } from "./backtrace.js";
|
|
3
|
+
import { filterParams } from "./filter.js";
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
|
|
6
|
+
export interface NoticeContext {
|
|
7
|
+
context?: Record<string, unknown>;
|
|
8
|
+
environment?: Record<string, unknown>;
|
|
9
|
+
session?: Record<string, unknown>;
|
|
10
|
+
params?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NoticePayload {
|
|
14
|
+
project_id?: string;
|
|
15
|
+
received_at: string;
|
|
16
|
+
errors: Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
message: string;
|
|
19
|
+
backtrace: BacktraceFrame[];
|
|
20
|
+
}>;
|
|
21
|
+
context: Record<string, unknown>;
|
|
22
|
+
environment: Record<string, unknown>;
|
|
23
|
+
session: Record<string, unknown>;
|
|
24
|
+
params: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildNotice(
|
|
28
|
+
error: Error,
|
|
29
|
+
configuration: Configuration,
|
|
30
|
+
options: NoticeContext = {},
|
|
31
|
+
): NoticePayload {
|
|
32
|
+
return {
|
|
33
|
+
project_id: configuration.projectId,
|
|
34
|
+
received_at: new Date().toISOString(),
|
|
35
|
+
errors: [
|
|
36
|
+
{
|
|
37
|
+
type: errorType(error),
|
|
38
|
+
message: String(error.message ?? ""),
|
|
39
|
+
backtrace: parseBacktrace(error),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
context: {
|
|
43
|
+
notifier: "errorgap-bun",
|
|
44
|
+
notifier_version: VERSION,
|
|
45
|
+
environment: configuration.environment,
|
|
46
|
+
release: configuration.release,
|
|
47
|
+
runtime: "bun",
|
|
48
|
+
runtime_version: tryBunVersion(),
|
|
49
|
+
...(options.context ?? {}),
|
|
50
|
+
},
|
|
51
|
+
environment: options.environment ?? {},
|
|
52
|
+
session: options.session ?? {},
|
|
53
|
+
params: filterParams(options.params ?? {}, configuration.filterKeys),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function tryBunVersion(): string | undefined {
|
|
58
|
+
try {
|
|
59
|
+
// @ts-ignore — Bun global is defined when running under Bun.
|
|
60
|
+
return typeof Bun !== "undefined" ? Bun.version : undefined;
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function errorType(error: Error): string {
|
|
67
|
+
if (typeof error.name === "string" && error.name.length > 0) {
|
|
68
|
+
return error.name;
|
|
69
|
+
}
|
|
70
|
+
return error.constructor?.name ?? "Error";
|
|
71
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "0.1.0";
|