@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/dist/context.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request correlation via Node **AsyncLocalStorage** (spec §D9, ADR-0025) — so a
|
|
4
|
+
* captured error carries the in-flight request's context without the developer
|
|
5
|
+
* threading a context object through every call.
|
|
6
|
+
*
|
|
7
|
+
* The request-context middleware opens a store holding the request/response refs and
|
|
8
|
+
* a correlation id; the transport reads {@link currentServerContext} at capture time,
|
|
9
|
+
* so the route pattern and status are read *late* (once the router has matched and the
|
|
10
|
+
* status is set), not at request start. `setRequestUser` lets the app attach its own
|
|
11
|
+
* user ref — the SDK never scrapes identity from the request itself.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.getRequestStore = getRequestStore;
|
|
15
|
+
exports.runWithRequestStore = runWithRequestStore;
|
|
16
|
+
exports.setRequestUser = setRequestUser;
|
|
17
|
+
exports.deriveServerContext = deriveServerContext;
|
|
18
|
+
exports.currentServerContext = currentServerContext;
|
|
19
|
+
exports.createRequestContextMiddleware = createRequestContextMiddleware;
|
|
20
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
21
|
+
const node_crypto_1 = require("node:crypto");
|
|
22
|
+
const http_1 = require("./http");
|
|
23
|
+
const storage = new node_async_hooks_1.AsyncLocalStorage();
|
|
24
|
+
/** The active request store, or `undefined` outside a request scope. */
|
|
25
|
+
function getRequestStore() {
|
|
26
|
+
return storage.getStore();
|
|
27
|
+
}
|
|
28
|
+
/** Run `fn` (and everything it awaits) with `store` as the active request store. */
|
|
29
|
+
function runWithRequestStore(store, fn) {
|
|
30
|
+
return storage.run(store, fn);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Attach an **app-supplied** user reference to the current request, so any error
|
|
34
|
+
* captured during it carries the user. A no-op outside a request scope (never
|
|
35
|
+
* throws). The SDK never derives identity from the request — only what you pass here.
|
|
36
|
+
*/
|
|
37
|
+
function setRequestUser(user) {
|
|
38
|
+
const store = storage.getStore();
|
|
39
|
+
if (store && typeof user === "string" && user.length > 0)
|
|
40
|
+
store.user = user;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Derive the private-by-default {@link ServerContext} from a request store: the HTTP
|
|
44
|
+
* method, the route **pattern**, the resolved status, the correlation id, and the
|
|
45
|
+
* app-supplied user ref. Returns `undefined` when there is no store or nothing
|
|
46
|
+
* resolvable — never the concrete path, a body, a header, or a query value.
|
|
47
|
+
*/
|
|
48
|
+
function deriveServerContext(store) {
|
|
49
|
+
if (!store)
|
|
50
|
+
return undefined;
|
|
51
|
+
const ctx = {};
|
|
52
|
+
const method = store.req.method;
|
|
53
|
+
if (typeof method === "string" && method.length > 0)
|
|
54
|
+
ctx.method = method;
|
|
55
|
+
const route = (0, http_1.routePatternOf)(store.req);
|
|
56
|
+
if (route)
|
|
57
|
+
ctx.route = route;
|
|
58
|
+
const status = store.res?.statusCode;
|
|
59
|
+
if (typeof status === "number" && status > 0)
|
|
60
|
+
ctx.statusCode = status;
|
|
61
|
+
if (store.requestId)
|
|
62
|
+
ctx.requestId = store.requestId;
|
|
63
|
+
if (store.user)
|
|
64
|
+
ctx.user = store.user;
|
|
65
|
+
return Object.keys(ctx).length > 0 ? ctx : undefined;
|
|
66
|
+
}
|
|
67
|
+
/** The {@link ServerContext} for the active request, or `undefined` outside one. */
|
|
68
|
+
function currentServerContext() {
|
|
69
|
+
return deriveServerContext(storage.getStore());
|
|
70
|
+
}
|
|
71
|
+
/** Read a correlation id from the configured header, taking the first if repeated. */
|
|
72
|
+
function readRequestId(req, header) {
|
|
73
|
+
const raw = req.headers?.[header];
|
|
74
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
75
|
+
if (typeof value !== "string")
|
|
76
|
+
return undefined;
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the Express-style request-context middleware: it opens an ALS store for the
|
|
82
|
+
* request (adopting an inbound correlation id, or minting one) and runs the rest of
|
|
83
|
+
* the request within it. Shared by the Express adapter and the NestJS module so both
|
|
84
|
+
* frameworks get per-request correlation the same way.
|
|
85
|
+
*/
|
|
86
|
+
function createRequestContextMiddleware(options = {}) {
|
|
87
|
+
const header = (options.requestIdHeader ?? "x-request-id").toLowerCase();
|
|
88
|
+
return function fixbackRequestContext(req, res, next) {
|
|
89
|
+
const store = {
|
|
90
|
+
req,
|
|
91
|
+
res,
|
|
92
|
+
requestId: readRequestId(req, header) ?? (0, node_crypto_1.randomUUID)(),
|
|
93
|
+
};
|
|
94
|
+
runWithRequestStore(store, () => next());
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAqBH,0CAEC;AAGD,kDAEC;AAOD,wCAGC;AAQD,kDAuBC;AAGD,oDAEC;AAuBD,wEAgBC;AA/GD,uDAAqD;AACrD,6CAAyC;AAEzC,iCAAwG;AAaxG,MAAM,OAAO,GAAG,IAAI,oCAAiB,EAAgB,CAAC;AAEtD,wEAAwE;AACxE,SAAgB,eAAe;IAC7B,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,oFAAoF;AACpF,SAAgB,mBAAmB,CAAI,KAAmB,EAAE,EAAW;IACrE,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjC,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,KAA+B;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,GAAG,GAML,EAAE,CAAC;IAEP,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;IAChC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IAEzE,MAAM,KAAK,GAAG,IAAA,qBAAc,EAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;IAE7B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC;IAEtE,IAAI,KAAK,CAAC,SAAS;QAAE,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IACrD,IAAI,KAAK,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAEtC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,oFAAoF;AACpF,SAAgB,oBAAoB;IAClC,OAAO,mBAAmB,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,sFAAsF;AACtF,SAAS,aAAa,CAAC,GAAoB,EAAE,MAAc;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAQD;;;;;GAKG;AACH,SAAgB,8BAA8B,CAC5C,UAAiC,EAAE;IAEnC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,eAAe,IAAI,cAAc,CAAC,CAAC,WAAW,EAAE,CAAC;IACzE,OAAO,SAAS,qBAAqB,CACnC,GAAoB,EACpB,GAAqB,EACrB,IAAkB;QAElB,MAAM,KAAK,GAAiB;YAC1B,GAAG;YACH,GAAG;YACH,SAAS,EAAE,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAA,wBAAU,GAAE;SACtD,CAAC;QACF,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **Express** adapter (`@fixback/node/express`, story 3) — two middlewares:
|
|
3
|
+
*
|
|
4
|
+
* - {@link fixbackRequestContext}: mount **before** your routes. It opens the
|
|
5
|
+
* per-request AsyncLocalStorage scope (correlation id + request/response refs) so a
|
|
6
|
+
* captured error carries the request without manual threading.
|
|
7
|
+
* - {@link fixbackErrorHandler}: mount **after** your routes (Express recognises a
|
|
8
|
+
* 4-arg middleware as an error handler). It captures an error that reaches
|
|
9
|
+
* `next(err)` with the request's server context (`handled: true`), then **calls
|
|
10
|
+
* `next(err)`** so your own error handling still runs — Fixback never swallows the
|
|
11
|
+
* error and never breaks the request.
|
|
12
|
+
*
|
|
13
|
+
* Uses only structural request/response types, so the SDK needs no `express`
|
|
14
|
+
* dependency; the real Express objects satisfy them.
|
|
15
|
+
*/
|
|
16
|
+
import { type CaptureClient } from "./client";
|
|
17
|
+
import { type HttpRequestLike, type HttpResponseLike, type NextFunction } from "./http";
|
|
18
|
+
/** An Express request-processing middleware (`(req, res, next)`). */
|
|
19
|
+
export type ExpressMiddleware = (req: HttpRequestLike, res: HttpResponseLike, next: NextFunction) => void;
|
|
20
|
+
/** An Express error-handling middleware (`(err, req, res, next)`). */
|
|
21
|
+
export type ExpressErrorMiddleware = (error: unknown, req: HttpRequestLike, res: HttpResponseLike, next: NextFunction) => void;
|
|
22
|
+
/** Options for {@link fixbackRequestContext}. */
|
|
23
|
+
export interface RequestContextOptions {
|
|
24
|
+
/** The header a correlation id is read from. Defaults to the client's config, then `x-request-id`. */
|
|
25
|
+
readonly requestIdHeader?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Options for {@link fixbackErrorHandler}. */
|
|
28
|
+
export interface ErrorHandlerOptions {
|
|
29
|
+
/** The client to file through. Defaults to the active module client (`init`). */
|
|
30
|
+
readonly client?: CaptureClient;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the request-context middleware. Mount it before your routes:
|
|
34
|
+
* `app.use(fixbackRequestContext())`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fixbackRequestContext(options?: RequestContextOptions): ExpressMiddleware;
|
|
37
|
+
/**
|
|
38
|
+
* Build the error-handling middleware. Mount it after your routes:
|
|
39
|
+
* `app.use(fixbackErrorHandler())`. Captures the error (with the request's server
|
|
40
|
+
* context, `handled: true`) and always forwards it via `next(err)`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function fixbackErrorHandler(options?: ErrorHandlerOptions): ExpressErrorMiddleware;
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The **Express** adapter (`@fixback/node/express`, story 3) — two middlewares:
|
|
4
|
+
*
|
|
5
|
+
* - {@link fixbackRequestContext}: mount **before** your routes. It opens the
|
|
6
|
+
* per-request AsyncLocalStorage scope (correlation id + request/response refs) so a
|
|
7
|
+
* captured error carries the request without manual threading.
|
|
8
|
+
* - {@link fixbackErrorHandler}: mount **after** your routes (Express recognises a
|
|
9
|
+
* 4-arg middleware as an error handler). It captures an error that reaches
|
|
10
|
+
* `next(err)` with the request's server context (`handled: true`), then **calls
|
|
11
|
+
* `next(err)`** so your own error handling still runs — Fixback never swallows the
|
|
12
|
+
* error and never breaks the request.
|
|
13
|
+
*
|
|
14
|
+
* Uses only structural request/response types, so the SDK needs no `express`
|
|
15
|
+
* dependency; the real Express objects satisfy them.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.fixbackRequestContext = fixbackRequestContext;
|
|
19
|
+
exports.fixbackErrorHandler = fixbackErrorHandler;
|
|
20
|
+
const client_1 = require("./client");
|
|
21
|
+
const context_1 = require("./context");
|
|
22
|
+
const http_1 = require("./http");
|
|
23
|
+
/**
|
|
24
|
+
* Build the request-context middleware. Mount it before your routes:
|
|
25
|
+
* `app.use(fixbackRequestContext())`.
|
|
26
|
+
*/
|
|
27
|
+
function fixbackRequestContext(options = {}) {
|
|
28
|
+
const header = options.requestIdHeader ?? (0, client_1.getClient)()?.requestIdHeader;
|
|
29
|
+
return (0, context_1.createRequestContextMiddleware)(header ? { requestIdHeader: header } : {});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the error-handling middleware. Mount it after your routes:
|
|
33
|
+
* `app.use(fixbackErrorHandler())`. Captures the error (with the request's server
|
|
34
|
+
* context, `handled: true`) and always forwards it via `next(err)`.
|
|
35
|
+
*/
|
|
36
|
+
function fixbackErrorHandler(options = {}) {
|
|
37
|
+
const client = options.client ?? {
|
|
38
|
+
captureException: (error, context) => (0, client_1.captureException)(error, context),
|
|
39
|
+
};
|
|
40
|
+
return function fixbackErrorHandlerMiddleware(error, req, res, next) {
|
|
41
|
+
try {
|
|
42
|
+
const server = { ...(0, http_1.serverContextFromRequest)(req), statusCode: (0, http_1.errorStatus)(error, res) };
|
|
43
|
+
client.captureException(error, { handled: true, server });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Capture must never break the request pipeline — always forward the error.
|
|
47
|
+
}
|
|
48
|
+
next(error);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AA+CH,sDAGC;AAOD,kDAaC;AApED,qCAIkB;AAClB,uCAA2D;AAC3D,iCAMgB;AA6BhB;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,UAAiC,EAAE;IACvE,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,IAAI,IAAA,kBAAS,GAAE,EAAE,eAAe,CAAC;IACvE,OAAO,IAAA,wCAA8B,EAAC,MAAM,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,UAA+B,EAAE;IACnE,MAAM,MAAM,GAAkB,OAAO,CAAC,MAAM,IAAI;QAC9C,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,IAAA,yBAAsB,EAAC,KAAK,EAAE,OAAO,CAAC;KAC7E,CAAC;IACF,OAAO,SAAS,6BAA6B,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI;QACjE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,EAAE,GAAG,IAAA,+BAAwB,EAAC,GAAG,CAAC,EAAE,UAAU,EAAE,IAAA,kBAAW,EAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;YACzF,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;QAC9E,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal **structural** types for the HTTP surface the adapters touch, plus
|
|
3
|
+
* {@link routePatternOf}.
|
|
4
|
+
*
|
|
5
|
+
* Described structurally (the same posture as `packages/expo/src/http.ts`) so the
|
|
6
|
+
* SDK needs no `@types/express` dependency: Express and NestJS (on Express) both pass
|
|
7
|
+
* requests/responses that satisfy these shapes, and tests pass plain objects. Only
|
|
8
|
+
* the private-by-default fields are read here — never bodies, headers beyond the
|
|
9
|
+
* correlation id, query values, or env.
|
|
10
|
+
*/
|
|
11
|
+
/** The request slice the adapters read — never its body, headers (bar the id), or query. */
|
|
12
|
+
export interface HttpRequestLike {
|
|
13
|
+
readonly method?: string;
|
|
14
|
+
readonly baseUrl?: string;
|
|
15
|
+
readonly path?: string;
|
|
16
|
+
/** Populated by the router once a route matches — its `path` is the **pattern**. */
|
|
17
|
+
readonly route?: {
|
|
18
|
+
readonly path?: string | RegExp;
|
|
19
|
+
} | undefined;
|
|
20
|
+
readonly headers?: Readonly<Record<string, string | string[] | undefined>>;
|
|
21
|
+
}
|
|
22
|
+
/** The response slice the adapters read — only the resolved status. */
|
|
23
|
+
export interface HttpResponseLike {
|
|
24
|
+
readonly statusCode?: number;
|
|
25
|
+
}
|
|
26
|
+
/** The Express/Nest `next` callback — `next(err)` forwards to the error pipeline. */
|
|
27
|
+
export type NextFunction = (err?: unknown) => void;
|
|
28
|
+
/**
|
|
29
|
+
* The route **pattern** for a request (`/api/users/:id`), joining a mounted router's
|
|
30
|
+
* `baseUrl` with the matched `route.path`. Returns `undefined` when no route has
|
|
31
|
+
* matched — deliberately **never** falling back to the concrete `path`, which would
|
|
32
|
+
* leak the in-URL values the PII boundary forbids (spec §D12).
|
|
33
|
+
*/
|
|
34
|
+
export declare function routePatternOf(req: HttpRequestLike): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The private-by-default server-context fields derivable from a request alone — the
|
|
37
|
+
* HTTP method and the route **pattern**. The single place that decides what an adapter
|
|
38
|
+
* reads off a request (never a body, header, query value, or the concrete path), so the
|
|
39
|
+
* Express and NestJS adapters can never drift on the privacy posture. The status is
|
|
40
|
+
* added by the caller (it differs: an Express error's own status vs a Nest
|
|
41
|
+
* `HttpException`'s).
|
|
42
|
+
*/
|
|
43
|
+
export declare function serverContextFromRequest(req: HttpRequestLike): {
|
|
44
|
+
method?: string;
|
|
45
|
+
route?: string;
|
|
46
|
+
};
|
|
47
|
+
/** Read an http-errors-style numeric status off a thrown value (`err.status`/`.statusCode`). */
|
|
48
|
+
export declare function numericStatus(error: unknown): number | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* A sensible status for a captured request error: the error's own status when set,
|
|
51
|
+
* else a 4xx/5xx already on the response, else `500`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function errorStatus(error: unknown, res: HttpResponseLike): number;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal **structural** types for the HTTP surface the adapters touch, plus
|
|
4
|
+
* {@link routePatternOf}.
|
|
5
|
+
*
|
|
6
|
+
* Described structurally (the same posture as `packages/expo/src/http.ts`) so the
|
|
7
|
+
* SDK needs no `@types/express` dependency: Express and NestJS (on Express) both pass
|
|
8
|
+
* requests/responses that satisfy these shapes, and tests pass plain objects. Only
|
|
9
|
+
* the private-by-default fields are read here — never bodies, headers beyond the
|
|
10
|
+
* correlation id, query values, or env.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.routePatternOf = routePatternOf;
|
|
14
|
+
exports.serverContextFromRequest = serverContextFromRequest;
|
|
15
|
+
exports.numericStatus = numericStatus;
|
|
16
|
+
exports.errorStatus = errorStatus;
|
|
17
|
+
/**
|
|
18
|
+
* The route **pattern** for a request (`/api/users/:id`), joining a mounted router's
|
|
19
|
+
* `baseUrl` with the matched `route.path`. Returns `undefined` when no route has
|
|
20
|
+
* matched — deliberately **never** falling back to the concrete `path`, which would
|
|
21
|
+
* leak the in-URL values the PII boundary forbids (spec §D12).
|
|
22
|
+
*/
|
|
23
|
+
function routePatternOf(req) {
|
|
24
|
+
const routePath = req.route?.path;
|
|
25
|
+
if (routePath === undefined || routePath === null)
|
|
26
|
+
return undefined;
|
|
27
|
+
const pattern = typeof routePath === "string" ? routePath : (routePath.source ?? String(routePath));
|
|
28
|
+
const base = typeof req.baseUrl === "string" ? req.baseUrl : "";
|
|
29
|
+
if (pattern.length === 0)
|
|
30
|
+
return base.length > 0 ? base : undefined;
|
|
31
|
+
if (base.length === 0)
|
|
32
|
+
return pattern;
|
|
33
|
+
return `${base}${pattern.startsWith("/") ? pattern : `/${pattern}`}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The private-by-default server-context fields derivable from a request alone — the
|
|
37
|
+
* HTTP method and the route **pattern**. The single place that decides what an adapter
|
|
38
|
+
* reads off a request (never a body, header, query value, or the concrete path), so the
|
|
39
|
+
* Express and NestJS adapters can never drift on the privacy posture. The status is
|
|
40
|
+
* added by the caller (it differs: an Express error's own status vs a Nest
|
|
41
|
+
* `HttpException`'s).
|
|
42
|
+
*/
|
|
43
|
+
function serverContextFromRequest(req) {
|
|
44
|
+
const out = {};
|
|
45
|
+
if (typeof req.method === "string" && req.method.length > 0)
|
|
46
|
+
out.method = req.method;
|
|
47
|
+
const route = routePatternOf(req);
|
|
48
|
+
if (route)
|
|
49
|
+
out.route = route;
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** Read an http-errors-style numeric status off a thrown value (`err.status`/`.statusCode`). */
|
|
53
|
+
function numericStatus(error) {
|
|
54
|
+
if (error && typeof error === "object") {
|
|
55
|
+
const e = error;
|
|
56
|
+
const raw = typeof e.status === "number" ? e.status : e.statusCode;
|
|
57
|
+
if (typeof raw === "number" && Number.isFinite(raw))
|
|
58
|
+
return raw;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A sensible status for a captured request error: the error's own status when set,
|
|
64
|
+
* else a 4xx/5xx already on the response, else `500`.
|
|
65
|
+
*/
|
|
66
|
+
function errorStatus(error, res) {
|
|
67
|
+
const fromError = numericStatus(error);
|
|
68
|
+
if (fromError && fromError >= 400)
|
|
69
|
+
return fromError;
|
|
70
|
+
const fromResponse = res.statusCode;
|
|
71
|
+
if (typeof fromResponse === "number" && fromResponse >= 400)
|
|
72
|
+
return fromResponse;
|
|
73
|
+
return 500;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AA0BH,wCASC;AAUD,4DAMC;AAGD,sCAOC;AAMD,kCAMC;AArDD;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,GAAoB;IACjD,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC;IAClC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpE,MAAM,OAAO,GACX,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IACtF,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACtC,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,EAAE,CAAC;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,wBAAwB,CAAC,GAAoB;IAC3D,MAAM,GAAG,GAAwC,EAAE,CAAC;IACpD,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IACrF,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;IAC7B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gGAAgG;AAChG,SAAgB,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,KAAmD,CAAC;QAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;QACnE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;IAClE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAc,EAAE,GAAqB;IAC/D,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,SAAS,IAAI,SAAS,IAAI,GAAG;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,YAAY,GAAG,GAAG,CAAC,UAAU,CAAC;IACpC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,IAAI,GAAG;QAAE,OAAO,YAAY,CAAC;IACjF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@fixback/node` — the Fixback backend error SDK for Node servers.
|
|
3
|
+
*
|
|
4
|
+
* `init({ secretKey })` wires a batched secret-key transport, the polite
|
|
5
|
+
* process-crash handlers, and the manual capture API below. Framework adapters are
|
|
6
|
+
* separate entry points so a plain install never pulls a framework in:
|
|
7
|
+
*
|
|
8
|
+
* - `@fixback/node/express` — {@link https://expressjs.com Express} middlewares.
|
|
9
|
+
* - `@fixback/node/nestjs` — a NestJS module + exception filter.
|
|
10
|
+
*
|
|
11
|
+
* The root entry is framework-free: import from here for `init`, manual capture,
|
|
12
|
+
* `setUser`, and the shared types.
|
|
13
|
+
*/
|
|
14
|
+
export { captureException, captureMessage, close, FixbackClient, flush, getClient, init, } from "./client";
|
|
15
|
+
export type { CaptureClient, CaptureContext, ClientDeps } from "./client";
|
|
16
|
+
export { currentServerContext, getRequestStore, setRequestUser, setRequestUser as setUser } from "./context";
|
|
17
|
+
export type { RequestStore } from "./context";
|
|
18
|
+
export { DEFAULT_API_URL } from "./config";
|
|
19
|
+
export type { InitOptions } from "./config";
|
|
20
|
+
export type { CaptureTransport } from "./transport";
|
|
21
|
+
export type { BeforeSend, FixbackErrorEvent, ServerContext, ServerErrorPayload, Severity, } from "./wire";
|
|
22
|
+
export type { CapturedFrame } from "@fixback/sdk-core";
|
|
23
|
+
export { NODE_SDK_VERSION } from "./version";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@fixback/node` — the Fixback backend error SDK for Node servers.
|
|
4
|
+
*
|
|
5
|
+
* `init({ secretKey })` wires a batched secret-key transport, the polite
|
|
6
|
+
* process-crash handlers, and the manual capture API below. Framework adapters are
|
|
7
|
+
* separate entry points so a plain install never pulls a framework in:
|
|
8
|
+
*
|
|
9
|
+
* - `@fixback/node/express` — {@link https://expressjs.com Express} middlewares.
|
|
10
|
+
* - `@fixback/node/nestjs` — a NestJS module + exception filter.
|
|
11
|
+
*
|
|
12
|
+
* The root entry is framework-free: import from here for `init`, manual capture,
|
|
13
|
+
* `setUser`, and the shared types.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.NODE_SDK_VERSION = exports.DEFAULT_API_URL = exports.setUser = exports.setRequestUser = exports.getRequestStore = exports.currentServerContext = exports.init = exports.getClient = exports.flush = exports.FixbackClient = exports.close = exports.captureMessage = exports.captureException = void 0;
|
|
17
|
+
// Capture surface + lifecycle.
|
|
18
|
+
var client_1 = require("./client");
|
|
19
|
+
Object.defineProperty(exports, "captureException", { enumerable: true, get: function () { return client_1.captureException; } });
|
|
20
|
+
Object.defineProperty(exports, "captureMessage", { enumerable: true, get: function () { return client_1.captureMessage; } });
|
|
21
|
+
Object.defineProperty(exports, "close", { enumerable: true, get: function () { return client_1.close; } });
|
|
22
|
+
Object.defineProperty(exports, "FixbackClient", { enumerable: true, get: function () { return client_1.FixbackClient; } });
|
|
23
|
+
Object.defineProperty(exports, "flush", { enumerable: true, get: function () { return client_1.flush; } });
|
|
24
|
+
Object.defineProperty(exports, "getClient", { enumerable: true, get: function () { return client_1.getClient; } });
|
|
25
|
+
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return client_1.init; } });
|
|
26
|
+
// Per-request context (AsyncLocalStorage) — attach a user, or read the active context.
|
|
27
|
+
var context_1 = require("./context");
|
|
28
|
+
Object.defineProperty(exports, "currentServerContext", { enumerable: true, get: function () { return context_1.currentServerContext; } });
|
|
29
|
+
Object.defineProperty(exports, "getRequestStore", { enumerable: true, get: function () { return context_1.getRequestStore; } });
|
|
30
|
+
Object.defineProperty(exports, "setRequestUser", { enumerable: true, get: function () { return context_1.setRequestUser; } });
|
|
31
|
+
Object.defineProperty(exports, "setUser", { enumerable: true, get: function () { return context_1.setRequestUser; } });
|
|
32
|
+
// Configuration.
|
|
33
|
+
var config_1 = require("./config");
|
|
34
|
+
Object.defineProperty(exports, "DEFAULT_API_URL", { enumerable: true, get: function () { return config_1.DEFAULT_API_URL; } });
|
|
35
|
+
// The SDK's own version, reported as the capture `sdkVersion`.
|
|
36
|
+
var version_1 = require("./version");
|
|
37
|
+
Object.defineProperty(exports, "NODE_SDK_VERSION", { enumerable: true, get: function () { return version_1.NODE_SDK_VERSION; } });
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,+BAA+B;AAC/B,mCAQkB;AAPhB,0GAAA,gBAAgB,OAAA;AAChB,wGAAA,cAAc,OAAA;AACd,+FAAA,KAAK,OAAA;AACL,uGAAA,aAAa,OAAA;AACb,+FAAA,KAAK,OAAA;AACL,mGAAA,SAAS,OAAA;AACT,8FAAA,IAAI,OAAA;AAIN,uFAAuF;AACvF,qCAA6G;AAApG,+GAAA,oBAAoB,OAAA;AAAE,0GAAA,eAAe,OAAA;AAAE,yGAAA,cAAc,OAAA;AAAE,kGAAA,cAAc,OAAW;AAGzF,iBAAiB;AACjB,mCAA2C;AAAlC,yGAAA,eAAe,OAAA;AAkBxB,+DAA+D;AAC/D,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA"}
|
package/dist/nestjs.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **NestJS** adapter (`@fixback/node/nestjs`, story 2) — a module + exception
|
|
3
|
+
* filter that capture thrown request errors with server context, **without changing
|
|
4
|
+
* the app's behaviour**.
|
|
5
|
+
*
|
|
6
|
+
* - {@link FixbackExceptionFilter} extends Nest's `BaseExceptionFilter`: it captures
|
|
7
|
+
* the exception (`handled: true`, with the request's route pattern / method /
|
|
8
|
+
* status) and then delegates to `super.catch`, so Nest still produces the exact
|
|
9
|
+
* HTTP response it would have. Registered globally via `APP_FILTER`.
|
|
10
|
+
* - {@link FixbackModule} wires that filter and applies the request-context
|
|
11
|
+
* middleware (AsyncLocalStorage correlation) to every route. Register it with
|
|
12
|
+
* `FixbackModule.forRoot()`.
|
|
13
|
+
*
|
|
14
|
+
* `@nestjs/common` and `@nestjs/core` are **optional peer dependencies** — this module
|
|
15
|
+
* is only loaded when you import `@fixback/node/nestjs`, so an Express-only or
|
|
16
|
+
* manual-capture install never pulls Nest in.
|
|
17
|
+
*/
|
|
18
|
+
import "reflect-metadata";
|
|
19
|
+
import { type ArgumentsHost, type DynamicModule, type MiddlewareConsumer, type NestModule } from "@nestjs/common";
|
|
20
|
+
import { BaseExceptionFilter } from "@nestjs/core";
|
|
21
|
+
import { type CaptureClient } from "./client";
|
|
22
|
+
/**
|
|
23
|
+
* Capture a Nest exception with the request's server context (`handled: true`).
|
|
24
|
+
* Exported so it can be unit-tested against a fake `ArgumentsHost`; the filter uses
|
|
25
|
+
* it. Never throws — a capture failure must not break the request pipeline.
|
|
26
|
+
*/
|
|
27
|
+
export declare function captureNestException(exception: unknown, host: ArgumentsHost, client?: CaptureClient): void;
|
|
28
|
+
/**
|
|
29
|
+
* A global exception filter that captures every thrown request error and then hands
|
|
30
|
+
* off to Nest's `BaseExceptionFilter`, so the HTTP response is exactly what Nest
|
|
31
|
+
* would have produced (no behaviour change).
|
|
32
|
+
*/
|
|
33
|
+
export declare class FixbackExceptionFilter extends BaseExceptionFilter {
|
|
34
|
+
catch(exception: unknown, host: ArgumentsHost): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The Fixback NestJS module. Register with `imports: [FixbackModule.forRoot()]`; it
|
|
38
|
+
* installs the global {@link FixbackExceptionFilter} and applies the request-context
|
|
39
|
+
* middleware to every route.
|
|
40
|
+
*/
|
|
41
|
+
export declare class FixbackModule implements NestModule {
|
|
42
|
+
static forRoot(): DynamicModule;
|
|
43
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
44
|
+
}
|
package/dist/nestjs.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The **NestJS** adapter (`@fixback/node/nestjs`, story 2) — a module + exception
|
|
4
|
+
* filter that capture thrown request errors with server context, **without changing
|
|
5
|
+
* the app's behaviour**.
|
|
6
|
+
*
|
|
7
|
+
* - {@link FixbackExceptionFilter} extends Nest's `BaseExceptionFilter`: it captures
|
|
8
|
+
* the exception (`handled: true`, with the request's route pattern / method /
|
|
9
|
+
* status) and then delegates to `super.catch`, so Nest still produces the exact
|
|
10
|
+
* HTTP response it would have. Registered globally via `APP_FILTER`.
|
|
11
|
+
* - {@link FixbackModule} wires that filter and applies the request-context
|
|
12
|
+
* middleware (AsyncLocalStorage correlation) to every route. Register it with
|
|
13
|
+
* `FixbackModule.forRoot()`.
|
|
14
|
+
*
|
|
15
|
+
* `@nestjs/common` and `@nestjs/core` are **optional peer dependencies** — this module
|
|
16
|
+
* is only loaded when you import `@fixback/node/nestjs`, so an Express-only or
|
|
17
|
+
* manual-capture install never pulls Nest in.
|
|
18
|
+
*/
|
|
19
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
20
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
22
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
23
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
24
|
+
};
|
|
25
|
+
var FixbackModule_1;
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.FixbackModule = exports.FixbackExceptionFilter = void 0;
|
|
28
|
+
exports.captureNestException = captureNestException;
|
|
29
|
+
require("reflect-metadata");
|
|
30
|
+
const common_1 = require("@nestjs/common");
|
|
31
|
+
const core_1 = require("@nestjs/core");
|
|
32
|
+
const client_1 = require("./client");
|
|
33
|
+
const context_1 = require("./context");
|
|
34
|
+
const http_1 = require("./http");
|
|
35
|
+
/** Files through the active module client by default. */
|
|
36
|
+
const defaultClient = {
|
|
37
|
+
captureException: (error, context) => (0, client_1.captureException)(error, context),
|
|
38
|
+
};
|
|
39
|
+
/** Resolve the status: an `HttpException`'s own status, else the shared heuristic. */
|
|
40
|
+
function nestStatus(exception, res) {
|
|
41
|
+
if (exception instanceof common_1.HttpException) {
|
|
42
|
+
const status = exception.getStatus();
|
|
43
|
+
if (typeof status === "number")
|
|
44
|
+
return status;
|
|
45
|
+
}
|
|
46
|
+
return (0, http_1.errorStatus)(exception, res);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Capture a Nest exception with the request's server context (`handled: true`).
|
|
50
|
+
* Exported so it can be unit-tested against a fake `ArgumentsHost`; the filter uses
|
|
51
|
+
* it. Never throws — a capture failure must not break the request pipeline.
|
|
52
|
+
*/
|
|
53
|
+
function captureNestException(exception, host, client = defaultClient) {
|
|
54
|
+
try {
|
|
55
|
+
const isHttp = typeof host.getType === "function" ? host.getType() === "http" : true;
|
|
56
|
+
if (!isHttp) {
|
|
57
|
+
client.captureException(exception, { handled: true });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const http = host.switchToHttp();
|
|
61
|
+
const req = http.getRequest();
|
|
62
|
+
const res = (http.getResponse() ?? {});
|
|
63
|
+
const base = req ? (0, http_1.serverContextFromRequest)(req) : {};
|
|
64
|
+
const server = { ...base, statusCode: nestStatus(exception, res) };
|
|
65
|
+
client.captureException(exception, { handled: true, server });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
try {
|
|
69
|
+
client.captureException(exception, { handled: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* capture must never break the pipeline */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A global exception filter that captures every thrown request error and then hands
|
|
78
|
+
* off to Nest's `BaseExceptionFilter`, so the HTTP response is exactly what Nest
|
|
79
|
+
* would have produced (no behaviour change).
|
|
80
|
+
*/
|
|
81
|
+
let FixbackExceptionFilter = class FixbackExceptionFilter extends core_1.BaseExceptionFilter {
|
|
82
|
+
catch(exception, host) {
|
|
83
|
+
captureNestException(exception, host);
|
|
84
|
+
super.catch(exception, host);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
exports.FixbackExceptionFilter = FixbackExceptionFilter;
|
|
88
|
+
exports.FixbackExceptionFilter = FixbackExceptionFilter = __decorate([
|
|
89
|
+
(0, common_1.Catch)()
|
|
90
|
+
], FixbackExceptionFilter);
|
|
91
|
+
/**
|
|
92
|
+
* The Fixback NestJS module. Register with `imports: [FixbackModule.forRoot()]`; it
|
|
93
|
+
* installs the global {@link FixbackExceptionFilter} and applies the request-context
|
|
94
|
+
* middleware to every route.
|
|
95
|
+
*/
|
|
96
|
+
let FixbackModule = FixbackModule_1 = class FixbackModule {
|
|
97
|
+
static forRoot() {
|
|
98
|
+
return {
|
|
99
|
+
module: FixbackModule_1,
|
|
100
|
+
providers: [{ provide: core_1.APP_FILTER, useClass: FixbackExceptionFilter }],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
configure(consumer) {
|
|
104
|
+
const requestIdHeader = (0, client_1.getClient)()?.requestIdHeader;
|
|
105
|
+
consumer
|
|
106
|
+
.apply((0, context_1.createRequestContextMiddleware)(requestIdHeader ? { requestIdHeader } : {}))
|
|
107
|
+
.forRoutes("*");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
exports.FixbackModule = FixbackModule;
|
|
111
|
+
exports.FixbackModule = FixbackModule = FixbackModule_1 = __decorate([
|
|
112
|
+
(0, common_1.Module)({})
|
|
113
|
+
], FixbackModule);
|
|
114
|
+
//# sourceMappingURL=nestjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nestjs.js","sourceRoot":"","sources":["../src/nestjs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level capture (spec §D9, stories 4 & 5) — the *polite* `uncaughtException`
|
|
3
|
+
* and `unhandledRejection` handlers that **never change the app's exit behaviour**.
|
|
4
|
+
*
|
|
5
|
+
* The subtlety: merely *adding* an `uncaughtException` listener suppresses Node's
|
|
6
|
+
* default crash. So to stay polite:
|
|
7
|
+
*
|
|
8
|
+
* - **uncaughtException:** capture (`handled: false`), then — only when we are the
|
|
9
|
+
* *sole* listener (the app installed none of its own) — best-effort flush and hand
|
|
10
|
+
* off to `onFatalError`, which preserves Node's default (log + non-zero exit). When
|
|
11
|
+
* the app has its own handler, we do nothing further: the app owns the exit.
|
|
12
|
+
* - **unhandledRejection:** capture (`handled: false`) and stop. We never escalate a
|
|
13
|
+
* rejection to a process exit — exactly the "never turn a logged rejection into an
|
|
14
|
+
* exit" guarantee (story 5).
|
|
15
|
+
*
|
|
16
|
+
* Everything is wrapped so a capture failure can never break the handler.
|
|
17
|
+
*/
|
|
18
|
+
/** The `process` slice these handlers touch — injectable so tests never touch the real one. */
|
|
19
|
+
export interface ProcessLike {
|
|
20
|
+
on(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
21
|
+
removeListener(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
22
|
+
listeners(event: string): Array<(...args: unknown[]) => void>;
|
|
23
|
+
}
|
|
24
|
+
/** What a captured process error is filed through — the {@link FixbackClient} satisfies it. */
|
|
25
|
+
export interface CaptureSink {
|
|
26
|
+
captureException(error: unknown, context?: {
|
|
27
|
+
handled?: boolean;
|
|
28
|
+
}): void;
|
|
29
|
+
flush(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Which process handlers to install. */
|
|
32
|
+
export interface ProcessHandlerOptions {
|
|
33
|
+
readonly captureUncaughtException: boolean;
|
|
34
|
+
readonly captureUnhandledRejection: boolean;
|
|
35
|
+
/** How long a fatal-path flush may take before the process exits anyway. */
|
|
36
|
+
readonly flushTimeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Injectable collaborators for {@link installProcessHandlers}. */
|
|
39
|
+
export interface ProcessHandlerDeps {
|
|
40
|
+
readonly process?: ProcessLike;
|
|
41
|
+
/**
|
|
42
|
+
* Preserve Node's default fatal behaviour after a sole-listener uncaught exception.
|
|
43
|
+
* Defaults to logging the error and exiting non-zero (exactly what Node would do).
|
|
44
|
+
*/
|
|
45
|
+
readonly onFatalError?: (error: unknown) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Install the polite process handlers for `sink`, returning an uninstall function.
|
|
49
|
+
* A no-op (and a no-op uninstall) when no `process` is available or when both gates
|
|
50
|
+
* are off.
|
|
51
|
+
*/
|
|
52
|
+
export declare function installProcessHandlers(sink: CaptureSink, options: ProcessHandlerOptions, deps?: ProcessHandlerDeps): () => void;
|