@fixback/node 0.2.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 +105 -0
- package/dist/client.d.ts +79 -0
- package/dist/client.js +220 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +71 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +53 -0
- package/dist/context.js +97 -0
- package/dist/context.js.map +1 -0
- package/dist/express.d.ts +42 -0
- package/dist/express.js +51 -0
- package/dist/express.js.map +1 -0
- package/dist/http.d.ts +53 -0
- package/dist/http.js +75 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/nestjs.d.ts +44 -0
- package/dist/nestjs.js +114 -0
- package/dist/nestjs.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/process.d.ts +52 -0
- package/dist/process.js +124 -0
- package/dist/process.js.map +1 -0
- package/dist/stack.d.ts +33 -0
- package/dist/stack.js +142 -0
- package/dist/stack.js.map +1 -0
- package/dist/transport.d.ts +90 -0
- package/dist/transport.js +172 -0
- package/dist/transport.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/dist/wire.d.ts +108 -0
- package/dist/wire.js +15 -0
- package/dist/wire.js.map +1 -0
- package/package.json +91 -0
- package/src/client.test.ts +272 -0
- package/src/client.ts +262 -0
- package/src/config.test.ts +79 -0
- package/src/config.ts +131 -0
- package/src/context.test.ts +99 -0
- package/src/context.ts +124 -0
- package/src/express.test.ts +112 -0
- package/src/express.ts +85 -0
- package/src/fingerprint-parity.test.ts +86 -0
- package/src/http.ts +83 -0
- package/src/index.ts +51 -0
- package/src/nestjs.test.ts +146 -0
- package/src/nestjs.ts +123 -0
- package/src/process.test.ts +154 -0
- package/src/process.ts +153 -0
- package/src/stack.test.ts +89 -0
- package/src/stack.ts +153 -0
- package/src/transport.test.ts +187 -0
- package/src/transport.ts +227 -0
- package/src/version.test.ts +10 -0
- package/src/version.ts +13 -0
- package/src/wire.ts +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fixback
|
|
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,105 @@
|
|
|
1
|
+
# @fixback/node
|
|
2
|
+
|
|
3
|
+
The Fixback **backend error SDK** for Node servers. Capture unhandled crashes and
|
|
4
|
+
manually-reported errors and file them into the same Fixback pipeline your frontend
|
|
5
|
+
uses — one queue for your whole product (ADR-0026/0027/0028, spec #224).
|
|
6
|
+
|
|
7
|
+
Backend errors authenticate with the Project **secret key**, so they arrive
|
|
8
|
+
**Internal** tier and tagged `platform: node`. Fleet-wide crash-loops collapse into a
|
|
9
|
+
single Issue with a rising occurrence count instead of flooding your queue.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @fixback/node
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The Express and NestJS adapters are optional — install the framework you use.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
Call `init` as early as possible in your process (before your app boots), so the
|
|
22
|
+
process handlers are installed before anything can throw:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// instrument.ts — imported first in your entry file
|
|
26
|
+
import { init } from "@fixback/node";
|
|
27
|
+
|
|
28
|
+
init({
|
|
29
|
+
secretKey: process.env.FIXBACK_SECRET_KEY!, // sk_… (never your publishable key)
|
|
30
|
+
environment: process.env.NODE_ENV, // "production" | "staging" | …
|
|
31
|
+
release: process.env.GIT_SHA, // symbolicated against your uploaded sourcemaps
|
|
32
|
+
enabled: process.env.NODE_ENV === "production", // gate auto-capture off in local dev
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That single call wires:
|
|
37
|
+
|
|
38
|
+
- a **batched** secret-key transport that backs off on `429` (honouring `Retry-After`);
|
|
39
|
+
- process-level `uncaughtException` / `unhandledRejection` capture that **never changes
|
|
40
|
+
your process's exit behaviour**;
|
|
41
|
+
- and the manual capture API below.
|
|
42
|
+
|
|
43
|
+
### Manual capture
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { captureException, captureMessage } from "@fixback/node";
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await risky();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
captureException(err); // handled: true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
captureMessage("cache miss storm", "warning");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Express
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import express from "express";
|
|
61
|
+
import { fixbackRequestContext, fixbackErrorHandler } from "@fixback/node/express";
|
|
62
|
+
|
|
63
|
+
const app = express();
|
|
64
|
+
app.use(fixbackRequestContext()); // opens per-request context (before your routes)
|
|
65
|
+
// … your routes …
|
|
66
|
+
app.use(fixbackErrorHandler()); // captures errors that reach next(err) (after your routes)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### NestJS
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { Module } from "@nestjs/common";
|
|
73
|
+
import { FixbackModule } from "@fixback/node/nestjs";
|
|
74
|
+
|
|
75
|
+
@Module({ imports: [FixbackModule.forRoot()] })
|
|
76
|
+
export class AppModule {}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`FixbackModule.forRoot()` registers the request-context middleware and a global
|
|
80
|
+
exception filter that captures thrown request errors and re-throws them, so your own
|
|
81
|
+
error handling is unchanged.
|
|
82
|
+
|
|
83
|
+
### Attaching a user
|
|
84
|
+
|
|
85
|
+
The SDK never scrapes user identity. Attach an **app-supplied** reference from inside a
|
|
86
|
+
request and it rides along with any error captured during that request:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { setUser } from "@fixback/node";
|
|
90
|
+
|
|
91
|
+
setUser(currentUser.id); // an opaque ref you choose — not a name/email unless you send one
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Privacy
|
|
95
|
+
|
|
96
|
+
Private-by-default. The SDK captures the route **pattern** (never the concrete path
|
|
97
|
+
with values), method, status, a correlation id, and the app-supplied user ref only. It
|
|
98
|
+
**never** sends request/response bodies, `Authorization`/`Cookie` headers, environment
|
|
99
|
+
variables, or query-string values. A `beforeSend(event)` hook lets you redact further
|
|
100
|
+
or drop an event entirely (return `null`).
|
|
101
|
+
|
|
102
|
+
## Fails quiet
|
|
103
|
+
|
|
104
|
+
If Fixback is unreachable or misconfigured, your server keeps running normally —
|
|
105
|
+
observability never becomes an outage.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `FixbackClient` and the module-level singleton API (`init`, `captureException`,
|
|
3
|
+
* `captureMessage`, `flush`, `close`) — the SDK's capture surface.
|
|
4
|
+
*
|
|
5
|
+
* A capture distils the error, fingerprints it with the **shared core**
|
|
6
|
+
* (`@fixback/sdk-core`, so the key matches the browser SDK — ADR-0028), attaches the
|
|
7
|
+
* in-flight request's {@link ServerContext} from AsyncLocalStorage, runs the scrub
|
|
8
|
+
* choke point and the project's `beforeSend`, then hands a {@link ServerErrorPayload}
|
|
9
|
+
* to the batched transport. Every step is wrapped so a capture **never throws into
|
|
10
|
+
* the host** (story 34): a misbehaving hook, an unserialisable error, or a transport
|
|
11
|
+
* hiccup is swallowed, and the server keeps running.
|
|
12
|
+
*/
|
|
13
|
+
import { type InitOptions, type ResolvedConfig } from "./config";
|
|
14
|
+
import { type ProcessLike } from "./process";
|
|
15
|
+
import { type CaptureTransport } from "./transport";
|
|
16
|
+
import type { ServerContext, Severity } from "./wire";
|
|
17
|
+
export type { BeforeSend, FixbackErrorEvent, ServerContext, Severity } from "./wire";
|
|
18
|
+
export type { InitOptions } from "./config";
|
|
19
|
+
export type { CaptureTransport } from "./transport";
|
|
20
|
+
/** Optional per-capture context — override `handled` or add/override server context. */
|
|
21
|
+
export interface CaptureContext {
|
|
22
|
+
/** Override the handled flag (defaults to `true` for a manual/caught capture). */
|
|
23
|
+
readonly handled?: boolean;
|
|
24
|
+
/** Server context to merge over what the request scope provides (e.g. a filter's status). */
|
|
25
|
+
readonly server?: ServerContext;
|
|
26
|
+
}
|
|
27
|
+
/** The minimal capture surface the framework adapters file through (satisfied by {@link FixbackClient}). */
|
|
28
|
+
export interface CaptureClient {
|
|
29
|
+
captureException(error: unknown, context?: CaptureContext): void;
|
|
30
|
+
}
|
|
31
|
+
/** Advanced/testing seam: supply a custom {@link CaptureTransport} or process. */
|
|
32
|
+
export interface ClientDeps {
|
|
33
|
+
readonly transport?: CaptureTransport;
|
|
34
|
+
/** The process to attach handlers to (defaults to the real `process`). */
|
|
35
|
+
readonly process?: ProcessLike;
|
|
36
|
+
/** Override Node's default fatal behaviour after a sole-listener uncaught exception. */
|
|
37
|
+
readonly onFatalError?: (error: unknown) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare class FixbackClient {
|
|
40
|
+
private readonly config;
|
|
41
|
+
private readonly transport;
|
|
42
|
+
constructor(config: ResolvedConfig, deps?: ClientDeps);
|
|
43
|
+
/** Whether this client will capture (its secret key resolved and the gate is on). */
|
|
44
|
+
get enabled(): boolean;
|
|
45
|
+
/** The header the request-context middleware reads a correlation id from. */
|
|
46
|
+
get requestIdHeader(): string;
|
|
47
|
+
/**
|
|
48
|
+
* Report a caught or manually-observed error. `handled` defaults to `true`; the
|
|
49
|
+
* process handlers pass `handled: false` for an uncaught crash.
|
|
50
|
+
*/
|
|
51
|
+
captureException(error: unknown, context?: CaptureContext): void;
|
|
52
|
+
/** Report a noteworthy non-error condition, grouped by its message text. */
|
|
53
|
+
captureMessage(message: string, level?: Severity, context?: CaptureContext): void;
|
|
54
|
+
/** Flush the transport's buffer now. Resolves even when the SDK is disabled. */
|
|
55
|
+
flush(): Promise<void>;
|
|
56
|
+
/** Stop the transport (its timer) and drain what remains. */
|
|
57
|
+
close(): Promise<void>;
|
|
58
|
+
/** Assemble, scrub, run `beforeSend`, and enqueue — swallowing any failure. */
|
|
59
|
+
private dispatch;
|
|
60
|
+
/** Run the project hook after the default scrubbers; a throwing hook is a no-op. */
|
|
61
|
+
private runBeforeSend;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Initialise the SDK: resolve the config, replace any previous client (closing it and
|
|
65
|
+
* removing its process handlers), install the polite process handlers, and return the
|
|
66
|
+
* active {@link FixbackClient}. Fail-quiet — a missing secret key yields an inert
|
|
67
|
+
* client (no handlers, no transport) rather than throwing.
|
|
68
|
+
*/
|
|
69
|
+
export declare function init(options: InitOptions, deps?: ClientDeps): FixbackClient;
|
|
70
|
+
/** The active client set by {@link init}, or `null` before the first `init`. */
|
|
71
|
+
export declare function getClient(): FixbackClient | null;
|
|
72
|
+
/** Report a caught/manual error through the active client (no-op before `init`). */
|
|
73
|
+
export declare function captureException(error: unknown, context?: CaptureContext): void;
|
|
74
|
+
/** Report a message through the active client (no-op before `init`). */
|
|
75
|
+
export declare function captureMessage(message: string, level?: Severity, context?: CaptureContext): void;
|
|
76
|
+
/** Flush the active client's buffer (no-op before `init`). */
|
|
77
|
+
export declare function flush(): Promise<void>;
|
|
78
|
+
/** Close and clear the active client, removing its process handlers (no-op before `init`). */
|
|
79
|
+
export declare function close(): Promise<void>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The `FixbackClient` and the module-level singleton API (`init`, `captureException`,
|
|
4
|
+
* `captureMessage`, `flush`, `close`) — the SDK's capture surface.
|
|
5
|
+
*
|
|
6
|
+
* A capture distils the error, fingerprints it with the **shared core**
|
|
7
|
+
* (`@fixback/sdk-core`, so the key matches the browser SDK — ADR-0028), attaches the
|
|
8
|
+
* in-flight request's {@link ServerContext} from AsyncLocalStorage, runs the scrub
|
|
9
|
+
* choke point and the project's `beforeSend`, then hands a {@link ServerErrorPayload}
|
|
10
|
+
* to the batched transport. Every step is wrapped so a capture **never throws into
|
|
11
|
+
* the host** (story 34): a misbehaving hook, an unserialisable error, or a transport
|
|
12
|
+
* hiccup is swallowed, and the server keeps running.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FixbackClient = void 0;
|
|
16
|
+
exports.init = init;
|
|
17
|
+
exports.getClient = getClient;
|
|
18
|
+
exports.captureException = captureException;
|
|
19
|
+
exports.captureMessage = captureMessage;
|
|
20
|
+
exports.flush = flush;
|
|
21
|
+
exports.close = close;
|
|
22
|
+
const sdk_core_1 = require("@fixback/sdk-core");
|
|
23
|
+
const config_1 = require("./config");
|
|
24
|
+
const context_1 = require("./context");
|
|
25
|
+
const process_1 = require("./process");
|
|
26
|
+
const stack_1 = require("./stack");
|
|
27
|
+
const transport_1 = require("./transport");
|
|
28
|
+
/** Merge two server contexts, the override winning; `undefined` when both are empty. */
|
|
29
|
+
function mergeServerContext(base, override) {
|
|
30
|
+
if (!base && !override)
|
|
31
|
+
return undefined;
|
|
32
|
+
const merged = { ...(base ?? {}), ...(override ?? {}) };
|
|
33
|
+
for (const key of Object.keys(merged)) {
|
|
34
|
+
if (merged[key] === undefined)
|
|
35
|
+
delete merged[key];
|
|
36
|
+
}
|
|
37
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
38
|
+
}
|
|
39
|
+
class FixbackClient {
|
|
40
|
+
config;
|
|
41
|
+
transport;
|
|
42
|
+
constructor(config, deps = {}) {
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.transport =
|
|
45
|
+
deps.transport ??
|
|
46
|
+
(config.enabled
|
|
47
|
+
? new transport_1.Transport({
|
|
48
|
+
endpoint: config.errorsEndpoint,
|
|
49
|
+
secretKey: config.secretKey,
|
|
50
|
+
maxBatchSize: config.maxBatchSize,
|
|
51
|
+
maxQueueSize: config.maxQueueSize,
|
|
52
|
+
flushIntervalMs: config.flushIntervalMs,
|
|
53
|
+
timeoutMs: config.timeoutMs,
|
|
54
|
+
})
|
|
55
|
+
: null);
|
|
56
|
+
}
|
|
57
|
+
/** Whether this client will capture (its secret key resolved and the gate is on). */
|
|
58
|
+
get enabled() {
|
|
59
|
+
return this.config.enabled;
|
|
60
|
+
}
|
|
61
|
+
/** The header the request-context middleware reads a correlation id from. */
|
|
62
|
+
get requestIdHeader() {
|
|
63
|
+
return this.config.requestIdHeader;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Report a caught or manually-observed error. `handled` defaults to `true`; the
|
|
67
|
+
* process handlers pass `handled: false` for an uncaught crash.
|
|
68
|
+
*/
|
|
69
|
+
captureException(error, context = {}) {
|
|
70
|
+
const { type, value, stack } = (0, stack_1.extractError)(error);
|
|
71
|
+
const frames = (0, stack_1.extractStructuredFrames)(stack);
|
|
72
|
+
this.dispatch({
|
|
73
|
+
errorSignature: (0, sdk_core_1.computeFingerprint)(type, value, stack),
|
|
74
|
+
type,
|
|
75
|
+
value,
|
|
76
|
+
handled: context.handled ?? true,
|
|
77
|
+
errorFrames: frames.length > 0 ? frames : undefined,
|
|
78
|
+
}, context);
|
|
79
|
+
}
|
|
80
|
+
/** Report a noteworthy non-error condition, grouped by its message text. */
|
|
81
|
+
captureMessage(message, level = "info", context = {}) {
|
|
82
|
+
const value = typeof message === "string" ? message : String(message);
|
|
83
|
+
this.dispatch({
|
|
84
|
+
errorSignature: (0, sdk_core_1.computeFingerprint)("Message", value),
|
|
85
|
+
type: "Message",
|
|
86
|
+
value,
|
|
87
|
+
level,
|
|
88
|
+
handled: context.handled ?? true,
|
|
89
|
+
}, context);
|
|
90
|
+
}
|
|
91
|
+
/** Flush the transport's buffer now. Resolves even when the SDK is disabled. */
|
|
92
|
+
flush() {
|
|
93
|
+
return this.transport?.flush() ?? Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
/** Stop the transport (its timer) and drain what remains. */
|
|
96
|
+
close() {
|
|
97
|
+
return this.transport?.close() ?? Promise.resolve();
|
|
98
|
+
}
|
|
99
|
+
/** Assemble, scrub, run `beforeSend`, and enqueue — swallowing any failure. */
|
|
100
|
+
dispatch(partial, context) {
|
|
101
|
+
if (!this.config.enabled || !this.transport)
|
|
102
|
+
return;
|
|
103
|
+
try {
|
|
104
|
+
const event = {
|
|
105
|
+
errorSignature: partial.errorSignature,
|
|
106
|
+
type: partial.type,
|
|
107
|
+
value: partial.value,
|
|
108
|
+
handled: partial.handled,
|
|
109
|
+
level: partial.level,
|
|
110
|
+
environment: this.config.environment,
|
|
111
|
+
release: this.config.release,
|
|
112
|
+
errorFrames: partial.errorFrames,
|
|
113
|
+
server: mergeServerContext((0, context_1.currentServerContext)(), context.server),
|
|
114
|
+
};
|
|
115
|
+
if (this.config.scrub)
|
|
116
|
+
applyDefaultScrub(event);
|
|
117
|
+
const finalised = this.runBeforeSend(event);
|
|
118
|
+
if (!finalised)
|
|
119
|
+
return; // dropped at the choke point — no transport
|
|
120
|
+
this.transport.enqueue(toPayload(finalised));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Capture must never throw into the host (story 34).
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Run the project hook after the default scrubbers; a throwing hook is a no-op. */
|
|
127
|
+
runBeforeSend(event) {
|
|
128
|
+
const hook = this.config.beforeSend;
|
|
129
|
+
if (!hook)
|
|
130
|
+
return event;
|
|
131
|
+
try {
|
|
132
|
+
return hook(event) ?? null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return event; // a buggy hook never breaks the report path nor leaks unscrubbed data
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.FixbackClient = FixbackClient;
|
|
140
|
+
/** The built-in default scrub: re-sweep frame URLs through the shared-core scrubber. */
|
|
141
|
+
function applyDefaultScrub(event) {
|
|
142
|
+
if (event.errorFrames && event.errorFrames.length > 0) {
|
|
143
|
+
event.errorFrames = event.errorFrames.map((frame) => ({
|
|
144
|
+
...frame,
|
|
145
|
+
file: (0, sdk_core_1.scrubUrl)(frame.file),
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Project the event onto the wire payload, dropping the event-only fields. */
|
|
150
|
+
function toPayload(event) {
|
|
151
|
+
const payload = { errorSignature: event.errorSignature };
|
|
152
|
+
if (event.handled !== undefined)
|
|
153
|
+
payload.handled = event.handled;
|
|
154
|
+
if (event.environment !== undefined)
|
|
155
|
+
payload.environment = event.environment;
|
|
156
|
+
if (event.release !== undefined)
|
|
157
|
+
payload.release = event.release;
|
|
158
|
+
if (event.occurrences !== undefined)
|
|
159
|
+
payload.occurrences = event.occurrences;
|
|
160
|
+
if (event.errorFrames && event.errorFrames.length > 0)
|
|
161
|
+
payload.errorFrames = event.errorFrames;
|
|
162
|
+
if (event.server)
|
|
163
|
+
payload.server = event.server;
|
|
164
|
+
return payload;
|
|
165
|
+
}
|
|
166
|
+
// --- Module-level singleton API ----------------------------------------------
|
|
167
|
+
let activeClient = null;
|
|
168
|
+
let activeUninstall = null;
|
|
169
|
+
/**
|
|
170
|
+
* Initialise the SDK: resolve the config, replace any previous client (closing it and
|
|
171
|
+
* removing its process handlers), install the polite process handlers, and return the
|
|
172
|
+
* active {@link FixbackClient}. Fail-quiet — a missing secret key yields an inert
|
|
173
|
+
* client (no handlers, no transport) rather than throwing.
|
|
174
|
+
*/
|
|
175
|
+
function init(options, deps) {
|
|
176
|
+
const previousClient = activeClient;
|
|
177
|
+
const previousUninstall = activeUninstall;
|
|
178
|
+
if (previousUninstall)
|
|
179
|
+
previousUninstall();
|
|
180
|
+
if (previousClient)
|
|
181
|
+
void previousClient.close();
|
|
182
|
+
const config = (0, config_1.resolveConfig)(options);
|
|
183
|
+
const client = new FixbackClient(config, deps);
|
|
184
|
+
activeClient = client;
|
|
185
|
+
activeUninstall =
|
|
186
|
+
config.enabled && (config.captureUncaughtException || config.captureUnhandledRejection)
|
|
187
|
+
? (0, process_1.installProcessHandlers)(client, {
|
|
188
|
+
captureUncaughtException: config.captureUncaughtException,
|
|
189
|
+
captureUnhandledRejection: config.captureUnhandledRejection,
|
|
190
|
+
}, { process: deps?.process, onFatalError: deps?.onFatalError })
|
|
191
|
+
: null;
|
|
192
|
+
return client;
|
|
193
|
+
}
|
|
194
|
+
/** The active client set by {@link init}, or `null` before the first `init`. */
|
|
195
|
+
function getClient() {
|
|
196
|
+
return activeClient;
|
|
197
|
+
}
|
|
198
|
+
/** Report a caught/manual error through the active client (no-op before `init`). */
|
|
199
|
+
function captureException(error, context) {
|
|
200
|
+
activeClient?.captureException(error, context);
|
|
201
|
+
}
|
|
202
|
+
/** Report a message through the active client (no-op before `init`). */
|
|
203
|
+
function captureMessage(message, level, context) {
|
|
204
|
+
activeClient?.captureMessage(message, level, context);
|
|
205
|
+
}
|
|
206
|
+
/** Flush the active client's buffer (no-op before `init`). */
|
|
207
|
+
function flush() {
|
|
208
|
+
return activeClient?.flush() ?? Promise.resolve();
|
|
209
|
+
}
|
|
210
|
+
/** Close and clear the active client, removing its process handlers (no-op before `init`). */
|
|
211
|
+
function close() {
|
|
212
|
+
const current = activeClient;
|
|
213
|
+
const uninstall = activeUninstall;
|
|
214
|
+
activeClient = null;
|
|
215
|
+
activeUninstall = null;
|
|
216
|
+
if (uninstall)
|
|
217
|
+
uninstall();
|
|
218
|
+
return current?.close() ?? Promise.resolve();
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `init` options and their resolution into a validated {@link ResolvedConfig}.
|
|
3
|
+
*
|
|
4
|
+
* Resolution is **fail-quiet** (spec #224, story 34): it never throws. A missing
|
|
5
|
+
* secret key or an explicit `enabled: false` leaves the SDK inert (`enabled: false`)
|
|
6
|
+
* rather than raising — a Fixback misconfiguration must never break the host server.
|
|
7
|
+
* Numeric knobs are clamped to safe bounds (the batch to the server's 500-item cap)
|
|
8
|
+
* instead of rejected.
|
|
9
|
+
*/
|
|
10
|
+
import type { BeforeSend } from "./wire";
|
|
11
|
+
/** The default Fixback ingest origin, shared with the browser and Expo SDKs. */
|
|
12
|
+
export declare const DEFAULT_API_URL = "https://api.fixback.dev";
|
|
13
|
+
/** Options for {@link init} — the one call that wires backend capture. */
|
|
14
|
+
export interface InitOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The Project **secret key** (`sk_…`) the backend authenticates with — the same
|
|
17
|
+
* key `npx fixback sourcemaps upload` uses (never the publishable key). Without it
|
|
18
|
+
* the SDK stays inert.
|
|
19
|
+
*/
|
|
20
|
+
readonly secretKey: string;
|
|
21
|
+
/** The Fixback ingest origin. Defaults to {@link DEFAULT_API_URL}. */
|
|
22
|
+
readonly apiUrl?: string;
|
|
23
|
+
/** The deploy environment (`production` / `staging` / …) stamped on every error. */
|
|
24
|
+
readonly environment?: string;
|
|
25
|
+
/** The build **Release** the captured stacks symbolicate against. */
|
|
26
|
+
readonly release?: string;
|
|
27
|
+
/** A hook to redact or drop an event before it leaves the process (return `null` to drop). */
|
|
28
|
+
readonly beforeSend?: BeforeSend;
|
|
29
|
+
/** Run the built-in default scrubbers before `beforeSend`. Defaults to `true`. */
|
|
30
|
+
readonly scrub?: boolean;
|
|
31
|
+
/** Master gate — set `false` to disable all capture (e.g. in local dev). Defaults to `true`. */
|
|
32
|
+
readonly enabled?: boolean;
|
|
33
|
+
/** Install the polite `uncaughtException` handler. Defaults to `true`. */
|
|
34
|
+
readonly captureUncaughtException?: boolean;
|
|
35
|
+
/** Install the polite `unhandledRejection` handler. Defaults to `true`. */
|
|
36
|
+
readonly captureUnhandledRejection?: boolean;
|
|
37
|
+
/** Flush the buffer once it reaches this many errors. Defaults to `100`; capped at `500`. */
|
|
38
|
+
readonly maxBatchSize?: number;
|
|
39
|
+
/** Flush a non-empty buffer at least this often, in ms. Defaults to `5000`. */
|
|
40
|
+
readonly flushIntervalMs?: number;
|
|
41
|
+
/** Cap the in-memory buffer so a flood can't grow it without bound. Defaults to `1024`. */
|
|
42
|
+
readonly maxQueueSize?: number;
|
|
43
|
+
/** Overall per-request transport timeout in ms; `0` disables. Defaults to `30000`. */
|
|
44
|
+
readonly timeoutMs?: number;
|
|
45
|
+
/** The header a correlation id is read from. Defaults to `x-request-id`. */
|
|
46
|
+
readonly requestIdHeader?: string;
|
|
47
|
+
}
|
|
48
|
+
/** The validated, defaulted configuration a {@link FixbackClient} runs on. */
|
|
49
|
+
export interface ResolvedConfig {
|
|
50
|
+
readonly enabled: boolean;
|
|
51
|
+
readonly secretKey: string;
|
|
52
|
+
readonly apiUrl: string;
|
|
53
|
+
readonly errorsEndpoint: string;
|
|
54
|
+
readonly environment?: string;
|
|
55
|
+
readonly release?: string;
|
|
56
|
+
readonly beforeSend?: BeforeSend;
|
|
57
|
+
readonly scrub: boolean;
|
|
58
|
+
readonly captureUncaughtException: boolean;
|
|
59
|
+
readonly captureUnhandledRejection: boolean;
|
|
60
|
+
readonly maxBatchSize: number;
|
|
61
|
+
readonly flushIntervalMs: number;
|
|
62
|
+
readonly maxQueueSize: number;
|
|
63
|
+
readonly timeoutMs: number;
|
|
64
|
+
readonly requestIdHeader: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve raw {@link InitOptions} into a validated {@link ResolvedConfig}. Never
|
|
68
|
+
* throws: without a usable secret key (or with `enabled: false`) the result is
|
|
69
|
+
* inert (`enabled: false`), so an inert client can no-op every capture.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveConfig(options: InitOptions): ResolvedConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The `init` options and their resolution into a validated {@link ResolvedConfig}.
|
|
4
|
+
*
|
|
5
|
+
* Resolution is **fail-quiet** (spec #224, story 34): it never throws. A missing
|
|
6
|
+
* secret key or an explicit `enabled: false` leaves the SDK inert (`enabled: false`)
|
|
7
|
+
* rather than raising — a Fixback misconfiguration must never break the host server.
|
|
8
|
+
* Numeric knobs are clamped to safe bounds (the batch to the server's 500-item cap)
|
|
9
|
+
* instead of rejected.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DEFAULT_API_URL = void 0;
|
|
13
|
+
exports.resolveConfig = resolveConfig;
|
|
14
|
+
/** The default Fixback ingest origin, shared with the browser and Expo SDKs. */
|
|
15
|
+
exports.DEFAULT_API_URL = "https://api.fixback.dev";
|
|
16
|
+
/** The largest batch the server-error endpoint accepts (`MAX_SERVER_ERROR_BATCH`). */
|
|
17
|
+
const SERVER_MAX_BATCH = 500;
|
|
18
|
+
const DEFAULT_MAX_BATCH_SIZE = 100;
|
|
19
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5_000;
|
|
20
|
+
const DEFAULT_MAX_QUEUE_SIZE = 1_024;
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
22
|
+
const DEFAULT_REQUEST_ID_HEADER = "x-request-id";
|
|
23
|
+
/** Trim a string option, returning `undefined` for a blank or non-string value. */
|
|
24
|
+
function trimmed(value) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
const out = value.trim();
|
|
28
|
+
return out.length > 0 ? out : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Normalise an api base URL: trim and strip trailing slashes. */
|
|
31
|
+
function normalizeApiUrl(value) {
|
|
32
|
+
const base = trimmed(value) ?? exports.DEFAULT_API_URL;
|
|
33
|
+
return base.replace(/\/+$/, "") || exports.DEFAULT_API_URL;
|
|
34
|
+
}
|
|
35
|
+
/** Clamp a positive-integer option to `[min, max]`, falling back to `fallback`. */
|
|
36
|
+
function clampInt(value, fallback, min, max) {
|
|
37
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
38
|
+
return fallback;
|
|
39
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve raw {@link InitOptions} into a validated {@link ResolvedConfig}. Never
|
|
43
|
+
* throws: without a usable secret key (or with `enabled: false`) the result is
|
|
44
|
+
* inert (`enabled: false`), so an inert client can no-op every capture.
|
|
45
|
+
*/
|
|
46
|
+
function resolveConfig(options) {
|
|
47
|
+
const secretKey = trimmed(options.secretKey) ?? "";
|
|
48
|
+
const apiUrl = normalizeApiUrl(options.apiUrl);
|
|
49
|
+
const enabled = options.enabled !== false && secretKey.length > 0;
|
|
50
|
+
return {
|
|
51
|
+
enabled,
|
|
52
|
+
secretKey,
|
|
53
|
+
apiUrl,
|
|
54
|
+
errorsEndpoint: `${apiUrl}/api/errors`,
|
|
55
|
+
environment: trimmed(options.environment),
|
|
56
|
+
release: trimmed(options.release),
|
|
57
|
+
beforeSend: typeof options.beforeSend === "function" ? options.beforeSend : undefined,
|
|
58
|
+
scrub: options.scrub !== false,
|
|
59
|
+
captureUncaughtException: options.captureUncaughtException !== false,
|
|
60
|
+
captureUnhandledRejection: options.captureUnhandledRejection !== false,
|
|
61
|
+
maxBatchSize: clampInt(options.maxBatchSize, DEFAULT_MAX_BATCH_SIZE, 1, SERVER_MAX_BATCH),
|
|
62
|
+
flushIntervalMs: clampInt(options.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS, 0, Number.MAX_SAFE_INTEGER),
|
|
63
|
+
maxQueueSize: clampInt(options.maxQueueSize, DEFAULT_MAX_QUEUE_SIZE, 1, Number.MAX_SAFE_INTEGER),
|
|
64
|
+
timeoutMs: clampInt(options.timeoutMs, DEFAULT_TIMEOUT_MS, 0, Number.MAX_SAFE_INTEGER),
|
|
65
|
+
requestIdHeader: (trimmed(options.requestIdHeader) ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request correlation via Node **AsyncLocalStorage** (spec §D9, ADR-0025) — so a
|
|
3
|
+
* captured error carries the in-flight request's context without the developer
|
|
4
|
+
* threading a context object through every call.
|
|
5
|
+
*
|
|
6
|
+
* The request-context middleware opens a store holding the request/response refs and
|
|
7
|
+
* a correlation id; the transport reads {@link currentServerContext} at capture time,
|
|
8
|
+
* so the route pattern and status are read *late* (once the router has matched and the
|
|
9
|
+
* status is set), not at request start. `setRequestUser` lets the app attach its own
|
|
10
|
+
* user ref — the SDK never scrapes identity from the request itself.
|
|
11
|
+
*/
|
|
12
|
+
import { type HttpRequestLike, type HttpResponseLike, type NextFunction } from "./http";
|
|
13
|
+
import type { ServerContext } from "./wire";
|
|
14
|
+
/** What the ALS store carries for one in-flight request. */
|
|
15
|
+
export interface RequestStore {
|
|
16
|
+
readonly req: HttpRequestLike;
|
|
17
|
+
readonly res?: HttpResponseLike;
|
|
18
|
+
/** The correlation id (inbound header when present, else generated). */
|
|
19
|
+
readonly requestId?: string;
|
|
20
|
+
/** The app-supplied user ref, written by {@link setRequestUser}. Never scraped. */
|
|
21
|
+
user?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The active request store, or `undefined` outside a request scope. */
|
|
24
|
+
export declare function getRequestStore(): RequestStore | undefined;
|
|
25
|
+
/** Run `fn` (and everything it awaits) with `store` as the active request store. */
|
|
26
|
+
export declare function runWithRequestStore<T>(store: RequestStore, fn: () => T): T;
|
|
27
|
+
/**
|
|
28
|
+
* Attach an **app-supplied** user reference to the current request, so any error
|
|
29
|
+
* captured during it carries the user. A no-op outside a request scope (never
|
|
30
|
+
* throws). The SDK never derives identity from the request — only what you pass here.
|
|
31
|
+
*/
|
|
32
|
+
export declare function setRequestUser(user: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Derive the private-by-default {@link ServerContext} from a request store: the HTTP
|
|
35
|
+
* method, the route **pattern**, the resolved status, the correlation id, and the
|
|
36
|
+
* app-supplied user ref. Returns `undefined` when there is no store or nothing
|
|
37
|
+
* resolvable — never the concrete path, a body, a header, or a query value.
|
|
38
|
+
*/
|
|
39
|
+
export declare function deriveServerContext(store: RequestStore | undefined): ServerContext | undefined;
|
|
40
|
+
/** The {@link ServerContext} for the active request, or `undefined` outside one. */
|
|
41
|
+
export declare function currentServerContext(): ServerContext | undefined;
|
|
42
|
+
/** Options for {@link createRequestContextMiddleware}. */
|
|
43
|
+
export interface RequestContextOptions {
|
|
44
|
+
/** The header a correlation id is read from. Defaults to `x-request-id`. */
|
|
45
|
+
readonly requestIdHeader?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the Express-style request-context middleware: it opens an ALS store for the
|
|
49
|
+
* request (adopting an inbound correlation id, or minting one) and runs the rest of
|
|
50
|
+
* the request within it. Shared by the Express adapter and the NestJS module so both
|
|
51
|
+
* frameworks get per-request correlation the same way.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createRequestContextMiddleware(options?: RequestContextOptions): (req: HttpRequestLike, res: HttpResponseLike, next: NextFunction) => void;
|