@beignet/provider-error-reporting-sentry 0.0.14

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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # @beignet/provider-error-reporting-sentry
2
+
3
+ ## 0.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 0b106c3: Add first-class error reporting primitives and a Sentry-backed error reporting
8
+ provider.
9
+
10
+ ## 0.0.13
11
+
12
+ ### Patch Changes
13
+
14
+ - Initial Sentry-backed error reporting provider.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Taylor Bryant
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,76 @@
1
+ # @beignet/provider-error-reporting-sentry
2
+
3
+ Sentry-backed `ErrorReporterPort` provider for Beignet applications.
4
+
5
+ The provider installs `ctx.ports.errorReporter` using `@sentry/node` while
6
+ keeping Sentry imports out of contracts, use cases, routes, and domain code.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ bun add @beignet/provider-error-reporting-sentry @sentry/node
12
+ ```
13
+
14
+ ## Register
15
+
16
+ ```typescript
17
+ // server/providers.ts
18
+ import { createSentryErrorReportingProvider } from "@beignet/provider-error-reporting-sentry";
19
+
20
+ export const providers = [
21
+ createSentryErrorReportingProvider({
22
+ dsn: process.env.SENTRY_DSN,
23
+ init: {
24
+ environment: process.env.NODE_ENV,
25
+ },
26
+ }),
27
+ ];
28
+ ```
29
+
30
+ With no `dsn` or `SENTRY_DSN`, the provider still contributes the Beignet port
31
+ but does not initialize Sentry. This is useful for examples and local apps that
32
+ want stable wiring before production secrets exist.
33
+
34
+ ## Use
35
+
36
+ ```typescript
37
+ await ctx.ports.errorReporter.captureException(error, {
38
+ level: "error",
39
+ requestId: ctx.requestId,
40
+ traceId: ctx.traceId,
41
+ tags: {
42
+ feature: "billing",
43
+ },
44
+ contexts: {
45
+ tenant: { id: ctx.tenant.id },
46
+ },
47
+ });
48
+ ```
49
+
50
+ The provider also contributes `ctx.ports.sentry` as an escape hatch with the raw
51
+ Sentry SDK and configured client.
52
+
53
+ ## Devtools
54
+
55
+ Captured exceptions and messages are recorded as Beignet error instrumentation
56
+ under the `errors` watcher when devtools or another instrumentation sink is
57
+ installed before the provider.
58
+
59
+ ## API
60
+
61
+ ### `createSentryErrorReporter(options)`
62
+
63
+ Creates an `ErrorReporterPort` from a Sentry-compatible client. Use this for
64
+ tests or custom provider composition.
65
+
66
+ ### `createSentryErrorReportingProvider(options)`
67
+
68
+ Creates a Beignet lifecycle provider that contributes:
69
+
70
+ - `ctx.ports.errorReporter`, the standard Beignet `ErrorReporterPort`
71
+ - `ctx.ports.sentry`, an escape hatch with the raw Sentry client and SDK
72
+
73
+ ### `sentryErrorReportingProvider`
74
+
75
+ Ready-to-register provider using the default Sentry singleton and
76
+ `process.env.SENTRY_DSN` when present.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @beignet/provider-error-reporting-sentry
3
+ *
4
+ * Sentry-backed error reporting provider for Beignet.
5
+ */
6
+ import type { ErrorReportContext, ErrorReporterFlushOptions, ErrorReporterPort, ErrorReportJsonValue, ErrorReportLevel, ErrorReportOptions, ErrorReportResult, ErrorReportTags, ErrorReportUser } from "@beignet/core/error-reporting";
7
+ import { type ProviderInstrumentationTarget } from "@beignet/core/providers";
8
+ import type { NodeOptions, Scope } from "@sentry/node";
9
+ import * as Sentry from "@sentry/node";
10
+ /**
11
+ * Sentry SDK surface used by the provider. Supplying this in tests avoids the
12
+ * process-wide singleton.
13
+ */
14
+ export interface SentryErrorReportingClient {
15
+ captureException(error: unknown): string | undefined;
16
+ captureMessage(message: string): string | undefined;
17
+ setUser(user: SentryErrorReportingUser | null): void;
18
+ setTags(tags: Record<string, string>): void;
19
+ setContext(name: string, context: ErrorReportContext | null): void;
20
+ flush(timeout?: number): PromiseLike<boolean> | boolean;
21
+ close?(timeout?: number): PromiseLike<boolean> | boolean;
22
+ withScope<T>(callback: (scope: Scope) => T): T;
23
+ init?(options?: NodeOptions): void;
24
+ }
25
+ /**
26
+ * Escape hatch for apps that need the raw Sentry SDK.
27
+ */
28
+ export interface SentryErrorReportingEscapeHatch {
29
+ client: SentryErrorReportingClient;
30
+ sdk: typeof Sentry;
31
+ }
32
+ export type SentryErrorReportingUser = Record<string, ErrorReportJsonValue | undefined>;
33
+ /**
34
+ * Ports contributed by the Sentry provider.
35
+ */
36
+ export interface SentryErrorReportingProviderPorts {
37
+ errorReporter: ErrorReporterPort;
38
+ sentry: SentryErrorReportingEscapeHatch;
39
+ }
40
+ /**
41
+ * Options for the direct Sentry reporter factory.
42
+ */
43
+ export interface CreateSentryErrorReporterOptions {
44
+ client?: SentryErrorReportingClient;
45
+ instrumentation?: ProviderInstrumentationTarget;
46
+ }
47
+ /**
48
+ * Options for the Sentry lifecycle provider.
49
+ */
50
+ export interface CreateSentryErrorReportingProviderOptions {
51
+ client?: SentryErrorReportingClient;
52
+ /**
53
+ * Sentry DSN. When omitted, the provider checks `process.env.SENTRY_DSN`.
54
+ * With no DSN, the SDK is not initialized and captures are effectively no-op
55
+ * until the app initializes Sentry elsewhere.
56
+ */
57
+ dsn?: string;
58
+ /**
59
+ * Additional Sentry initialization options.
60
+ */
61
+ init?: Omit<NodeOptions, "dsn">;
62
+ /**
63
+ * Close the Sentry client on server stop.
64
+ *
65
+ * @default false
66
+ */
67
+ closeOnStop?: boolean;
68
+ /**
69
+ * Flush/close timeout in milliseconds.
70
+ *
71
+ * @default 2000
72
+ */
73
+ flushTimeoutMs?: number;
74
+ }
75
+ /**
76
+ * Create a Beignet error reporter backed by Sentry.
77
+ */
78
+ export declare function createSentryErrorReporter(options?: CreateSentryErrorReporterOptions): ErrorReporterPort;
79
+ /**
80
+ * Create a Beignet provider that contributes `ports.errorReporter`.
81
+ */
82
+ export declare function createSentryErrorReportingProvider(options?: CreateSentryErrorReportingProviderOptions): import("@beignet/core/providers").ServiceProvider<unknown, import("@standard-schema/spec").StandardSchemaV1<void, void>, {
83
+ errorReporter: ErrorReporterPort;
84
+ sentry: {
85
+ client: typeof Sentry | SentryErrorReportingClient;
86
+ sdk: typeof Sentry;
87
+ };
88
+ }, unknown, void>;
89
+ /**
90
+ * Ready-to-register Sentry error reporting provider using the default Sentry
91
+ * singleton.
92
+ */
93
+ export declare const sentryErrorReportingProvider: import("@beignet/core/providers").ServiceProvider<unknown, import("@standard-schema/spec").StandardSchemaV1<void, void>, {
94
+ errorReporter: ErrorReporterPort;
95
+ sentry: {
96
+ client: typeof Sentry | SentryErrorReportingClient;
97
+ sdk: typeof Sentry;
98
+ };
99
+ }, unknown, void>;
100
+ export type { ErrorReportContext, ErrorReporterFlushOptions, ErrorReporterPort, ErrorReportJsonValue, ErrorReportLevel, ErrorReportOptions, ErrorReportResult, ErrorReportTags, ErrorReportUser, };
101
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAiB,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACrD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,OAAO,CAAC,IAAI,EAAE,wBAAwB,GAAG,IAAI,GAAG,IAAI,CAAC;IACrD,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC5C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;IACnE,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACzD,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,0BAA0B,CAAC;IACnC,GAAG,EAAE,OAAO,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAC3C,MAAM,EACN,oBAAoB,GAAG,SAAS,CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAChD,aAAa,EAAE,iBAAiB,CAAC;IACjC,MAAM,EAAE,+BAA+B,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC/C,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,yCAAyC;IACxD,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,iBAAiB,CAmFnB;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,GAAE,yCAA8C;;;;;;kBAgCxD;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;iBACH,CAAC;AAmGvC,YAAY,EACV,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @beignet/provider-error-reporting-sentry
3
+ *
4
+ * Sentry-backed error reporting provider for Beignet.
5
+ */
6
+ import { createProvider, createProviderInstrumentation, } from "@beignet/core/providers";
7
+ import * as Sentry from "@sentry/node";
8
+ /**
9
+ * Create a Beignet error reporter backed by Sentry.
10
+ */
11
+ export function createSentryErrorReporter(options = {}) {
12
+ const client = options.client ?? Sentry;
13
+ const instrumentation = createProviderInstrumentation(options.instrumentation, {
14
+ providerName: "error-reporting-sentry",
15
+ watcher: "errors",
16
+ });
17
+ return {
18
+ async captureException(error, reportOptions) {
19
+ const startedAt = Date.now();
20
+ const id = withConfiguredScope(client, reportOptions, () => client.captureException(error));
21
+ instrumentation.record({
22
+ type: "error",
23
+ watcher: "errors",
24
+ owner: "provider",
25
+ message: errorMessage(error),
26
+ stack: errorStack(error),
27
+ requestId: reportOptions?.requestId,
28
+ traceId: reportOptions?.traceId,
29
+ spanId: reportOptions?.spanId,
30
+ parentSpanId: reportOptions?.parentSpanId,
31
+ traceparent: reportOptions?.traceparent,
32
+ details: {
33
+ provider: "sentry",
34
+ eventId: id,
35
+ level: reportOptions?.level ?? "error",
36
+ durationMs: Date.now() - startedAt,
37
+ tags: reportOptions?.tags,
38
+ contexts: reportOptions?.contexts,
39
+ extra: reportOptions?.extra,
40
+ mechanism: reportOptions?.mechanism,
41
+ handled: reportOptions?.handled,
42
+ },
43
+ });
44
+ return result(id);
45
+ },
46
+ async captureMessage(message, reportOptions) {
47
+ const startedAt = Date.now();
48
+ const id = withConfiguredScope(client, reportOptions, () => client.captureMessage(message));
49
+ instrumentation.record({
50
+ type: "error",
51
+ watcher: "errors",
52
+ owner: "provider",
53
+ message,
54
+ requestId: reportOptions?.requestId,
55
+ traceId: reportOptions?.traceId,
56
+ spanId: reportOptions?.spanId,
57
+ parentSpanId: reportOptions?.parentSpanId,
58
+ traceparent: reportOptions?.traceparent,
59
+ details: {
60
+ provider: "sentry",
61
+ eventId: id,
62
+ level: reportOptions?.level ?? "error",
63
+ durationMs: Date.now() - startedAt,
64
+ tags: reportOptions?.tags,
65
+ contexts: reportOptions?.contexts,
66
+ extra: reportOptions?.extra,
67
+ mechanism: reportOptions?.mechanism,
68
+ handled: reportOptions?.handled,
69
+ },
70
+ });
71
+ return result(id);
72
+ },
73
+ setUser(user) {
74
+ client.setUser(toSentryUser(user));
75
+ },
76
+ setTags(tags) {
77
+ client.setTags(normalizeTags(tags));
78
+ },
79
+ setContext(name, context) {
80
+ client.setContext(name, context);
81
+ },
82
+ async flush(flushOptions) {
83
+ return Boolean(await client.flush(flushOptions?.timeoutMs));
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Create a Beignet provider that contributes `ports.errorReporter`.
89
+ */
90
+ export function createSentryErrorReportingProvider(options = {}) {
91
+ return createProvider({
92
+ name: "error-reporting-sentry",
93
+ metadata: {
94
+ packageName: "@beignet/provider-error-reporting-sentry",
95
+ ports: ["errorReporter", "sentry"],
96
+ watchers: ["errors"],
97
+ },
98
+ setup({ ports }) {
99
+ const client = options.client ?? Sentry;
100
+ initializeSentry(client, options);
101
+ return {
102
+ ports: {
103
+ errorReporter: createSentryErrorReporter({
104
+ client,
105
+ instrumentation: ports,
106
+ }),
107
+ sentry: {
108
+ client,
109
+ sdk: Sentry,
110
+ },
111
+ },
112
+ async stop() {
113
+ if (options.closeOnStop && client.close) {
114
+ await client.close(options.flushTimeoutMs ?? 2000);
115
+ }
116
+ },
117
+ };
118
+ },
119
+ });
120
+ }
121
+ /**
122
+ * Ready-to-register Sentry error reporting provider using the default Sentry
123
+ * singleton.
124
+ */
125
+ export const sentryErrorReportingProvider = createSentryErrorReportingProvider();
126
+ function initializeSentry(client, options) {
127
+ if (!client.init)
128
+ return;
129
+ const dsn = options.dsn ?? process.env.SENTRY_DSN;
130
+ if (!dsn && !options.init)
131
+ return;
132
+ client.init({
133
+ ...options.init,
134
+ dsn,
135
+ });
136
+ }
137
+ function withConfiguredScope(client, options, capture) {
138
+ return client.withScope((scope) => {
139
+ applyScope(scope, options);
140
+ return capture();
141
+ });
142
+ }
143
+ function applyScope(scope, options) {
144
+ if (!options)
145
+ return;
146
+ if (options.level)
147
+ scope.setLevel(toSentryLevel(options.level));
148
+ if (options.user !== undefined)
149
+ scope.setUser(toSentryUser(options.user));
150
+ if (options.tags)
151
+ scope.setTags(normalizeTags(options.tags));
152
+ if (options.extra)
153
+ scope.setExtras(stripUndefined(options.extra));
154
+ if (options.fingerprint)
155
+ scope.setFingerprint([...options.fingerprint]);
156
+ for (const [name, context] of Object.entries(options.contexts ?? {})) {
157
+ if (context !== undefined) {
158
+ scope.setContext(name, stripUndefined(context));
159
+ }
160
+ }
161
+ scope.setContext("beignet", stripUndefined({
162
+ requestId: options.requestId,
163
+ traceId: options.traceId,
164
+ spanId: options.spanId,
165
+ parentSpanId: options.parentSpanId,
166
+ traceparent: options.traceparent,
167
+ mechanism: options.mechanism,
168
+ handled: options.handled,
169
+ }));
170
+ }
171
+ function toSentryLevel(level) {
172
+ return level === "warning" ? "warning" : level;
173
+ }
174
+ function normalizeTags(tags) {
175
+ return Object.fromEntries(Object.entries(tags)
176
+ .filter(([, value]) => value !== undefined)
177
+ .map(([key, value]) => [key, String(value)]));
178
+ }
179
+ function toSentryUser(user) {
180
+ if (user === null)
181
+ return null;
182
+ const { ipAddress, ...rest } = user;
183
+ return stripUndefined({
184
+ ...rest,
185
+ ip_address: ipAddress,
186
+ });
187
+ }
188
+ function stripUndefined(value) {
189
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
190
+ }
191
+ function errorMessage(error) {
192
+ return error instanceof Error ? error.message : String(error);
193
+ }
194
+ function errorStack(error) {
195
+ return error instanceof Error ? error.stack : undefined;
196
+ }
197
+ function result(id) {
198
+ return id ? { id } : {};
199
+ }
200
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@beignet/provider-error-reporting-sentry",
3
+ "version": "0.0.14",
4
+ "type": "module",
5
+ "description": "Sentry error reporting provider for Beignet",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "!src/**/*.test.ts",
18
+ "!src/**/*.test.tsx",
19
+ "!src/**/*.test-d.ts",
20
+ "README.md",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsc --watch",
26
+ "clean": "rm -rf dist coverage .turbo",
27
+ "test": "bun test",
28
+ "test:coverage": "bun test --coverage",
29
+ "lint": "biome check ."
30
+ },
31
+ "keywords": [
32
+ "beignet",
33
+ "sentry",
34
+ "error-reporting",
35
+ "provider",
36
+ "ports"
37
+ ],
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/taylorbryant/beignet.git",
42
+ "directory": "packages/provider-error-reporting-sentry"
43
+ },
44
+ "author": "Taylor Bryant",
45
+ "homepage": "https://github.com/taylorbryant/beignet#readme",
46
+ "bugs": "https://github.com/taylorbryant/beignet/issues",
47
+ "sideEffects": false,
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "peerDependencies": {
55
+ "@beignet/core": ">=0.0.3 <1.0.0",
56
+ "@sentry/node": "^10.58.0"
57
+ },
58
+ "devDependencies": {
59
+ "@beignet/core": "*",
60
+ "@beignet/devtools": "*",
61
+ "@sentry/node": "^10.58.0",
62
+ "@types/bun": "^1.3.13",
63
+ "@types/node": "^20.10.0",
64
+ "typescript": "^5.3.0"
65
+ },
66
+ "beignet": {
67
+ "provider": {
68
+ "displayName": "Sentry error reporting provider",
69
+ "ports": [
70
+ "errorReporter",
71
+ "sentry"
72
+ ],
73
+ "appPorts": [
74
+ {
75
+ "name": "errorReporter",
76
+ "type": "ErrorReporterPort"
77
+ }
78
+ ],
79
+ "env": [
80
+ "SENTRY_DSN"
81
+ ],
82
+ "requiredEnv": [],
83
+ "watchers": [
84
+ "errors"
85
+ ],
86
+ "registration": {
87
+ "required": true,
88
+ "tokens": [
89
+ "sentryErrorReportingProvider",
90
+ "createSentryErrorReportingProvider"
91
+ ]
92
+ }
93
+ }
94
+ }
95
+ }
package/src/index.ts ADDED
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @beignet/provider-error-reporting-sentry
3
+ *
4
+ * Sentry-backed error reporting provider for Beignet.
5
+ */
6
+
7
+ import type {
8
+ ErrorReportContext,
9
+ ErrorReporterFlushOptions,
10
+ ErrorReporterPort,
11
+ ErrorReportJsonValue,
12
+ ErrorReportLevel,
13
+ ErrorReportOptions,
14
+ ErrorReportResult,
15
+ ErrorReportTags,
16
+ ErrorReportUser,
17
+ } from "@beignet/core/error-reporting";
18
+ import {
19
+ createProvider,
20
+ createProviderInstrumentation,
21
+ type ProviderInstrumentationTarget,
22
+ } from "@beignet/core/providers";
23
+ import type { NodeOptions, Scope, SeverityLevel } from "@sentry/node";
24
+ import * as Sentry from "@sentry/node";
25
+
26
+ /**
27
+ * Sentry SDK surface used by the provider. Supplying this in tests avoids the
28
+ * process-wide singleton.
29
+ */
30
+ export interface SentryErrorReportingClient {
31
+ captureException(error: unknown): string | undefined;
32
+ captureMessage(message: string): string | undefined;
33
+ setUser(user: SentryErrorReportingUser | null): void;
34
+ setTags(tags: Record<string, string>): void;
35
+ setContext(name: string, context: ErrorReportContext | null): void;
36
+ flush(timeout?: number): PromiseLike<boolean> | boolean;
37
+ close?(timeout?: number): PromiseLike<boolean> | boolean;
38
+ withScope<T>(callback: (scope: Scope) => T): T;
39
+ init?(options?: NodeOptions): void;
40
+ }
41
+
42
+ /**
43
+ * Escape hatch for apps that need the raw Sentry SDK.
44
+ */
45
+ export interface SentryErrorReportingEscapeHatch {
46
+ client: SentryErrorReportingClient;
47
+ sdk: typeof Sentry;
48
+ }
49
+
50
+ export type SentryErrorReportingUser = Record<
51
+ string,
52
+ ErrorReportJsonValue | undefined
53
+ >;
54
+
55
+ /**
56
+ * Ports contributed by the Sentry provider.
57
+ */
58
+ export interface SentryErrorReportingProviderPorts {
59
+ errorReporter: ErrorReporterPort;
60
+ sentry: SentryErrorReportingEscapeHatch;
61
+ }
62
+
63
+ /**
64
+ * Options for the direct Sentry reporter factory.
65
+ */
66
+ export interface CreateSentryErrorReporterOptions {
67
+ client?: SentryErrorReportingClient;
68
+ instrumentation?: ProviderInstrumentationTarget;
69
+ }
70
+
71
+ /**
72
+ * Options for the Sentry lifecycle provider.
73
+ */
74
+ export interface CreateSentryErrorReportingProviderOptions {
75
+ client?: SentryErrorReportingClient;
76
+ /**
77
+ * Sentry DSN. When omitted, the provider checks `process.env.SENTRY_DSN`.
78
+ * With no DSN, the SDK is not initialized and captures are effectively no-op
79
+ * until the app initializes Sentry elsewhere.
80
+ */
81
+ dsn?: string;
82
+ /**
83
+ * Additional Sentry initialization options.
84
+ */
85
+ init?: Omit<NodeOptions, "dsn">;
86
+ /**
87
+ * Close the Sentry client on server stop.
88
+ *
89
+ * @default false
90
+ */
91
+ closeOnStop?: boolean;
92
+ /**
93
+ * Flush/close timeout in milliseconds.
94
+ *
95
+ * @default 2000
96
+ */
97
+ flushTimeoutMs?: number;
98
+ }
99
+
100
+ /**
101
+ * Create a Beignet error reporter backed by Sentry.
102
+ */
103
+ export function createSentryErrorReporter(
104
+ options: CreateSentryErrorReporterOptions = {},
105
+ ): ErrorReporterPort {
106
+ const client = options.client ?? Sentry;
107
+ const instrumentation = createProviderInstrumentation(
108
+ options.instrumentation,
109
+ {
110
+ providerName: "error-reporting-sentry",
111
+ watcher: "errors",
112
+ },
113
+ );
114
+
115
+ return {
116
+ async captureException(error, reportOptions) {
117
+ const startedAt = Date.now();
118
+ const id = withConfiguredScope(client, reportOptions, () =>
119
+ client.captureException(error),
120
+ );
121
+ instrumentation.record({
122
+ type: "error",
123
+ watcher: "errors",
124
+ owner: "provider",
125
+ message: errorMessage(error),
126
+ stack: errorStack(error),
127
+ requestId: reportOptions?.requestId,
128
+ traceId: reportOptions?.traceId,
129
+ spanId: reportOptions?.spanId,
130
+ parentSpanId: reportOptions?.parentSpanId,
131
+ traceparent: reportOptions?.traceparent,
132
+ details: {
133
+ provider: "sentry",
134
+ eventId: id,
135
+ level: reportOptions?.level ?? "error",
136
+ durationMs: Date.now() - startedAt,
137
+ tags: reportOptions?.tags,
138
+ contexts: reportOptions?.contexts,
139
+ extra: reportOptions?.extra,
140
+ mechanism: reportOptions?.mechanism,
141
+ handled: reportOptions?.handled,
142
+ },
143
+ });
144
+ return result(id);
145
+ },
146
+ async captureMessage(message, reportOptions) {
147
+ const startedAt = Date.now();
148
+ const id = withConfiguredScope(client, reportOptions, () =>
149
+ client.captureMessage(message),
150
+ );
151
+ instrumentation.record({
152
+ type: "error",
153
+ watcher: "errors",
154
+ owner: "provider",
155
+ message,
156
+ requestId: reportOptions?.requestId,
157
+ traceId: reportOptions?.traceId,
158
+ spanId: reportOptions?.spanId,
159
+ parentSpanId: reportOptions?.parentSpanId,
160
+ traceparent: reportOptions?.traceparent,
161
+ details: {
162
+ provider: "sentry",
163
+ eventId: id,
164
+ level: reportOptions?.level ?? "error",
165
+ durationMs: Date.now() - startedAt,
166
+ tags: reportOptions?.tags,
167
+ contexts: reportOptions?.contexts,
168
+ extra: reportOptions?.extra,
169
+ mechanism: reportOptions?.mechanism,
170
+ handled: reportOptions?.handled,
171
+ },
172
+ });
173
+ return result(id);
174
+ },
175
+ setUser(user) {
176
+ client.setUser(toSentryUser(user));
177
+ },
178
+ setTags(tags) {
179
+ client.setTags(normalizeTags(tags));
180
+ },
181
+ setContext(name, context) {
182
+ client.setContext(name, context);
183
+ },
184
+ async flush(flushOptions) {
185
+ return Boolean(await client.flush(flushOptions?.timeoutMs));
186
+ },
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Create a Beignet provider that contributes `ports.errorReporter`.
192
+ */
193
+ export function createSentryErrorReportingProvider(
194
+ options: CreateSentryErrorReportingProviderOptions = {},
195
+ ) {
196
+ return createProvider({
197
+ name: "error-reporting-sentry",
198
+ metadata: {
199
+ packageName: "@beignet/provider-error-reporting-sentry",
200
+ ports: ["errorReporter", "sentry"],
201
+ watchers: ["errors"],
202
+ },
203
+ setup({ ports }) {
204
+ const client = options.client ?? Sentry;
205
+ initializeSentry(client, options);
206
+
207
+ return {
208
+ ports: {
209
+ errorReporter: createSentryErrorReporter({
210
+ client,
211
+ instrumentation: ports,
212
+ }),
213
+ sentry: {
214
+ client,
215
+ sdk: Sentry,
216
+ },
217
+ } satisfies SentryErrorReportingProviderPorts,
218
+ async stop() {
219
+ if (options.closeOnStop && client.close) {
220
+ await client.close(options.flushTimeoutMs ?? 2000);
221
+ }
222
+ },
223
+ };
224
+ },
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Ready-to-register Sentry error reporting provider using the default Sentry
230
+ * singleton.
231
+ */
232
+ export const sentryErrorReportingProvider =
233
+ createSentryErrorReportingProvider();
234
+
235
+ function initializeSentry(
236
+ client: SentryErrorReportingClient,
237
+ options: CreateSentryErrorReportingProviderOptions,
238
+ ) {
239
+ if (!client.init) return;
240
+ const dsn = options.dsn ?? process.env.SENTRY_DSN;
241
+ if (!dsn && !options.init) return;
242
+
243
+ client.init({
244
+ ...options.init,
245
+ dsn,
246
+ });
247
+ }
248
+
249
+ function withConfiguredScope<T>(
250
+ client: SentryErrorReportingClient,
251
+ options: ErrorReportOptions | undefined,
252
+ capture: () => T,
253
+ ): T {
254
+ return client.withScope((scope) => {
255
+ applyScope(scope, options);
256
+ return capture();
257
+ });
258
+ }
259
+
260
+ function applyScope(scope: Scope, options: ErrorReportOptions | undefined) {
261
+ if (!options) return;
262
+ if (options.level) scope.setLevel(toSentryLevel(options.level));
263
+ if (options.user !== undefined) scope.setUser(toSentryUser(options.user));
264
+ if (options.tags) scope.setTags(normalizeTags(options.tags));
265
+ if (options.extra) scope.setExtras(stripUndefined(options.extra));
266
+ if (options.fingerprint) scope.setFingerprint([...options.fingerprint]);
267
+
268
+ for (const [name, context] of Object.entries(options.contexts ?? {})) {
269
+ if (context !== undefined) {
270
+ scope.setContext(name, stripUndefined(context));
271
+ }
272
+ }
273
+
274
+ scope.setContext(
275
+ "beignet",
276
+ stripUndefined({
277
+ requestId: options.requestId,
278
+ traceId: options.traceId,
279
+ spanId: options.spanId,
280
+ parentSpanId: options.parentSpanId,
281
+ traceparent: options.traceparent,
282
+ mechanism: options.mechanism,
283
+ handled: options.handled,
284
+ }),
285
+ );
286
+ }
287
+
288
+ function toSentryLevel(level: ErrorReportLevel): SeverityLevel {
289
+ return level === "warning" ? "warning" : level;
290
+ }
291
+
292
+ function normalizeTags(tags: ErrorReportTags): Record<string, string> {
293
+ return Object.fromEntries(
294
+ Object.entries(tags)
295
+ .filter(([, value]) => value !== undefined)
296
+ .map(([key, value]) => [key, String(value)]),
297
+ );
298
+ }
299
+
300
+ function toSentryUser(
301
+ user: ErrorReportUser | null,
302
+ ): SentryErrorReportingUser | null {
303
+ if (user === null) return null;
304
+ const { ipAddress, ...rest } = user;
305
+
306
+ return stripUndefined({
307
+ ...rest,
308
+ ip_address: ipAddress,
309
+ }) as SentryErrorReportingUser;
310
+ }
311
+
312
+ function stripUndefined(
313
+ value: Record<string, unknown>,
314
+ ): Record<string, unknown> {
315
+ return Object.fromEntries(
316
+ Object.entries(value).filter(([, entry]) => entry !== undefined),
317
+ );
318
+ }
319
+
320
+ function errorMessage(error: unknown): string {
321
+ return error instanceof Error ? error.message : String(error);
322
+ }
323
+
324
+ function errorStack(error: unknown): string | undefined {
325
+ return error instanceof Error ? error.stack : undefined;
326
+ }
327
+
328
+ function result(id: string | undefined): ErrorReportResult {
329
+ return id ? { id } : {};
330
+ }
331
+
332
+ export type {
333
+ ErrorReportContext,
334
+ ErrorReporterFlushOptions,
335
+ ErrorReporterPort,
336
+ ErrorReportJsonValue,
337
+ ErrorReportLevel,
338
+ ErrorReportOptions,
339
+ ErrorReportResult,
340
+ ErrorReportTags,
341
+ ErrorReportUser,
342
+ };