@vigilon/nextjs 0.0.0-stage → 0.2.15
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 +15 -0
- package/README.md +113 -2
- package/dist/config.cjs +170 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.mts +51 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.mjs +138 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.cjs +86 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.mjs +58 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.cjs +224 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.mts +116 -0
- package/dist/server.d.ts +116 -0
- package/dist/server.mjs +180 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +101 -3
- package/register.cjs +5 -0
- package/register.mjs +12 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/client/index.ts
|
|
23
|
+
var client_exports = {};
|
|
24
|
+
__export(client_exports, {
|
|
25
|
+
init: () => init,
|
|
26
|
+
linkServerTrace: () => linkServerTrace,
|
|
27
|
+
useReportError: () => useReportError
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(client_exports);
|
|
30
|
+
__reExport(client_exports, require("@vigilon/react"), module.exports);
|
|
31
|
+
|
|
32
|
+
// src/client/init.ts
|
|
33
|
+
var import_react = require("@vigilon/react");
|
|
34
|
+
function init(options) {
|
|
35
|
+
const serverTrace = readServerTrace();
|
|
36
|
+
if (serverTrace === null) {
|
|
37
|
+
return (0, import_react.init)(options);
|
|
38
|
+
}
|
|
39
|
+
const { beforeSend } = options;
|
|
40
|
+
return (0, import_react.init)({
|
|
41
|
+
...options,
|
|
42
|
+
beforeSend: (event) => {
|
|
43
|
+
const linked = linkServerTrace(event, serverTrace);
|
|
44
|
+
return beforeSend ? beforeSend(linked) : linked;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function readServerTrace() {
|
|
49
|
+
var _a;
|
|
50
|
+
if (typeof document === "undefined") {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const content = (_a = document.querySelector('meta[name="traceparent"]')) == null ? void 0 : _a.getAttribute("content");
|
|
54
|
+
const traceId = content == null ? void 0 : content.split("-")[1];
|
|
55
|
+
if (!traceId || !/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return { traceId, url: withoutFragment(window.location.href) };
|
|
59
|
+
}
|
|
60
|
+
function linkServerTrace(event, serverTrace) {
|
|
61
|
+
if (event.traceId || withoutFragment(String(event.page.url)) !== serverTrace.url) {
|
|
62
|
+
return event;
|
|
63
|
+
}
|
|
64
|
+
return { ...event, traceId: serverTrace.traceId };
|
|
65
|
+
}
|
|
66
|
+
function withoutFragment(url) {
|
|
67
|
+
const hash = url.indexOf("#");
|
|
68
|
+
return hash === -1 ? url : url.slice(0, hash);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/client/use-report-error.ts
|
|
72
|
+
var import_react2 = require("@vigilon/react");
|
|
73
|
+
var import_react3 = require("react");
|
|
74
|
+
function useReportError(error) {
|
|
75
|
+
(0, import_react3.useEffect)(() => {
|
|
76
|
+
(0, import_react2.captureException)(error, { mechanism: "react_error_boundary", handled: true });
|
|
77
|
+
}, [error]);
|
|
78
|
+
}
|
|
79
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
80
|
+
0 && (module.exports = {
|
|
81
|
+
init,
|
|
82
|
+
linkServerTrace,
|
|
83
|
+
useReportError,
|
|
84
|
+
...require("@vigilon/react")
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/index.ts","../src/client/init.ts","../src/client/use-report-error.ts"],"sourcesContent":["/**\n * @vigilon/nextjs: the browser half, for instrumentation-client.ts and client\n * components. Re-exports @vigilon/react, with an init() that links events to\n * the server request that rendered the page.\n */\nexport * from \"@vigilon/react\";\nexport { init, linkServerTrace } from \"./init\";\nexport { useReportError } from \"./use-report-error\";\n","import { type BrowserEvent, init as initBrowser, type VigilonBrowserOptions } from \"@vigilon/react\";\n\n/**\n * Starts error monitoring in the browser, as @vigilon/browser's init() does.\n * With `withVigilon()` in next.config, Next.js renders the trace ID of the\n * request that rendered the page in a `traceparent` meta tag; every event\n * captured before the first client-side navigation carries it, so a hydration\n * or early render error links to its server trace.\n */\nexport function init(options: VigilonBrowserOptions): boolean {\n const serverTrace = readServerTrace();\n if (serverTrace === null) {\n return initBrowser(options);\n }\n const { beforeSend } = options;\n return initBrowser({\n ...options,\n beforeSend: (event) => {\n const linked = linkServerTrace(event, serverTrace);\n return beforeSend ? beforeSend(linked) : linked;\n },\n });\n}\n\ninterface ServerTrace {\n traceId: string;\n /** The page URL the server rendered, without its fragment. */\n url: string;\n}\n\nfunction readServerTrace(): ServerTrace | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const content = document.querySelector('meta[name=\"traceparent\"]')?.getAttribute(\"content\");\n // W3C traceparent: version-traceid-parentid-flags.\n const traceId = content?.split(\"-\")[1];\n if (!traceId || !/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) {\n return null;\n }\n return { traceId, url: withoutFragment(window.location.href) };\n}\n\n/**\n * The trace belongs to the request that rendered the page, so it applies only\n * while the browser is still on that page's URL.\n */\nexport function linkServerTrace(event: BrowserEvent, serverTrace: ServerTrace): BrowserEvent {\n if (event.traceId || withoutFragment(String(event.page.url)) !== serverTrace.url) {\n return event;\n }\n return { ...event, traceId: serverTrace.traceId };\n}\n\nfunction withoutFragment(url: string): string {\n const hash = url.indexOf(\"#\");\n return hash === -1 ? url : url.slice(0, hash);\n}\n","import { captureException } from \"@vigilon/react\";\nimport { useEffect } from \"react\";\n\n/**\n * Reports the error an `error.tsx` or `global-error.tsx` boundary received.\n * Next.js creates the React root itself, so @vigilon/react's\n * rootErrorHandlers cannot be passed to it; this hook reports what those\n * boundaries catch instead.\n *\n * ```tsx\n * \"use client\";\n * export default function GlobalError({ error }: { error: Error & { digest?: string } }) {\n * useReportError(error);\n * return <html><body><h2>Something went wrong.</h2></body></html>;\n * }\n * ```\n */\nexport function useReportError(error: unknown): void {\n useEffect(() => {\n captureException(error, { mechanism: \"react_error_boundary\", handled: true });\n }, [error]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,2BAAc,2BALd;;;ACAA,mBAAmF;AAS5E,SAAS,KAAK,SAAyC;AAC5D,QAAM,cAAc,gBAAgB;AACpC,MAAI,gBAAgB,MAAM;AACxB,eAAO,aAAAA,MAAY,OAAO;AAAA,EAC5B;AACA,QAAM,EAAE,WAAW,IAAI;AACvB,aAAO,aAAAA,MAAY;AAAA,IACjB,GAAG;AAAA,IACH,YAAY,CAAC,UAAU;AACrB,YAAM,SAAS,gBAAgB,OAAO,WAAW;AACjD,aAAO,aAAa,WAAW,MAAM,IAAI;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;AAQA,SAAS,kBAAsC;AA9B/C;AA+BE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AACA,QAAM,WAAU,cAAS,cAAc,0BAA0B,MAAjD,mBAAoD,aAAa;AAEjF,QAAM,UAAU,mCAAS,MAAM,KAAK;AACpC,MAAI,CAAC,WAAW,CAAC,iBAAiB,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO,EAAE,SAAS,KAAK,gBAAgB,OAAO,SAAS,IAAI,EAAE;AAC/D;AAMO,SAAS,gBAAgB,OAAqB,aAAwC;AAC3F,MAAI,MAAM,WAAW,gBAAgB,OAAO,MAAM,KAAK,GAAG,CAAC,MAAM,YAAY,KAAK;AAChF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,GAAG,OAAO,SAAS,YAAY,QAAQ;AAClD;AAEA,SAAS,gBAAgB,KAAqB;AAC5C,QAAM,OAAO,IAAI,QAAQ,GAAG;AAC5B,SAAO,SAAS,KAAK,MAAM,IAAI,MAAM,GAAG,IAAI;AAC9C;;;ACzDA,IAAAC,gBAAiC;AACjC,IAAAA,gBAA0B;AAgBnB,SAAS,eAAe,OAAsB;AACnD,+BAAU,MAAM;AACd,wCAAiB,OAAO,EAAE,WAAW,wBAAwB,SAAS,KAAK,CAAC;AAAA,EAC9E,GAAG,CAAC,KAAK,CAAC;AACZ;","names":["initBrowser","import_react"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { VigilonBrowserOptions, BrowserEvent } from '@vigilon/react';
|
|
2
|
+
export * from '@vigilon/react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Starts error monitoring in the browser, as @vigilon/browser's init() does.
|
|
6
|
+
* With `withVigilon()` in next.config, Next.js renders the trace ID of the
|
|
7
|
+
* request that rendered the page in a `traceparent` meta tag; every event
|
|
8
|
+
* captured before the first client-side navigation carries it, so a hydration
|
|
9
|
+
* or early render error links to its server trace.
|
|
10
|
+
*/
|
|
11
|
+
declare function init(options: VigilonBrowserOptions): boolean;
|
|
12
|
+
interface ServerTrace {
|
|
13
|
+
traceId: string;
|
|
14
|
+
/** The page URL the server rendered, without its fragment. */
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The trace belongs to the request that rendered the page, so it applies only
|
|
19
|
+
* while the browser is still on that page's URL.
|
|
20
|
+
*/
|
|
21
|
+
declare function linkServerTrace(event: BrowserEvent, serverTrace: ServerTrace): BrowserEvent;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reports the error an `error.tsx` or `global-error.tsx` boundary received.
|
|
25
|
+
* Next.js creates the React root itself, so @vigilon/react's
|
|
26
|
+
* rootErrorHandlers cannot be passed to it; this hook reports what those
|
|
27
|
+
* boundaries catch instead.
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* "use client";
|
|
31
|
+
* export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
|
32
|
+
* useReportError(error);
|
|
33
|
+
* return <html><body><h2>Something went wrong.</h2></body></html>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function useReportError(error: unknown): void;
|
|
38
|
+
|
|
39
|
+
export { init, linkServerTrace, useReportError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { VigilonBrowserOptions, BrowserEvent } from '@vigilon/react';
|
|
2
|
+
export * from '@vigilon/react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Starts error monitoring in the browser, as @vigilon/browser's init() does.
|
|
6
|
+
* With `withVigilon()` in next.config, Next.js renders the trace ID of the
|
|
7
|
+
* request that rendered the page in a `traceparent` meta tag; every event
|
|
8
|
+
* captured before the first client-side navigation carries it, so a hydration
|
|
9
|
+
* or early render error links to its server trace.
|
|
10
|
+
*/
|
|
11
|
+
declare function init(options: VigilonBrowserOptions): boolean;
|
|
12
|
+
interface ServerTrace {
|
|
13
|
+
traceId: string;
|
|
14
|
+
/** The page URL the server rendered, without its fragment. */
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The trace belongs to the request that rendered the page, so it applies only
|
|
19
|
+
* while the browser is still on that page's URL.
|
|
20
|
+
*/
|
|
21
|
+
declare function linkServerTrace(event: BrowserEvent, serverTrace: ServerTrace): BrowserEvent;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reports the error an `error.tsx` or `global-error.tsx` boundary received.
|
|
25
|
+
* Next.js creates the React root itself, so @vigilon/react's
|
|
26
|
+
* rootErrorHandlers cannot be passed to it; this hook reports what those
|
|
27
|
+
* boundaries catch instead.
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* "use client";
|
|
31
|
+
* export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
|
32
|
+
* useReportError(error);
|
|
33
|
+
* return <html><body><h2>Something went wrong.</h2></body></html>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function useReportError(error: unknown): void;
|
|
38
|
+
|
|
39
|
+
export { init, linkServerTrace, useReportError };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/client/index.ts
|
|
4
|
+
export * from "@vigilon/react";
|
|
5
|
+
|
|
6
|
+
// src/client/init.ts
|
|
7
|
+
import { init as initBrowser } from "@vigilon/react";
|
|
8
|
+
function init(options) {
|
|
9
|
+
const serverTrace = readServerTrace();
|
|
10
|
+
if (serverTrace === null) {
|
|
11
|
+
return initBrowser(options);
|
|
12
|
+
}
|
|
13
|
+
const { beforeSend } = options;
|
|
14
|
+
return initBrowser({
|
|
15
|
+
...options,
|
|
16
|
+
beforeSend: (event) => {
|
|
17
|
+
const linked = linkServerTrace(event, serverTrace);
|
|
18
|
+
return beforeSend ? beforeSend(linked) : linked;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function readServerTrace() {
|
|
23
|
+
var _a;
|
|
24
|
+
if (typeof document === "undefined") {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const content = (_a = document.querySelector('meta[name="traceparent"]')) == null ? void 0 : _a.getAttribute("content");
|
|
28
|
+
const traceId = content == null ? void 0 : content.split("-")[1];
|
|
29
|
+
if (!traceId || !/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return { traceId, url: withoutFragment(window.location.href) };
|
|
33
|
+
}
|
|
34
|
+
function linkServerTrace(event, serverTrace) {
|
|
35
|
+
if (event.traceId || withoutFragment(String(event.page.url)) !== serverTrace.url) {
|
|
36
|
+
return event;
|
|
37
|
+
}
|
|
38
|
+
return { ...event, traceId: serverTrace.traceId };
|
|
39
|
+
}
|
|
40
|
+
function withoutFragment(url) {
|
|
41
|
+
const hash = url.indexOf("#");
|
|
42
|
+
return hash === -1 ? url : url.slice(0, hash);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/client/use-report-error.ts
|
|
46
|
+
import { captureException } from "@vigilon/react";
|
|
47
|
+
import { useEffect } from "react";
|
|
48
|
+
function useReportError(error) {
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
captureException(error, { mechanism: "react_error_boundary", handled: true });
|
|
51
|
+
}, [error]);
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
init,
|
|
55
|
+
linkServerTrace,
|
|
56
|
+
useReportError
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/index.ts","../src/client/init.ts","../src/client/use-report-error.ts"],"sourcesContent":["/**\n * @vigilon/nextjs: the browser half, for instrumentation-client.ts and client\n * components. Re-exports @vigilon/react, with an init() that links events to\n * the server request that rendered the page.\n */\nexport * from \"@vigilon/react\";\nexport { init, linkServerTrace } from \"./init\";\nexport { useReportError } from \"./use-report-error\";\n","import { type BrowserEvent, init as initBrowser, type VigilonBrowserOptions } from \"@vigilon/react\";\n\n/**\n * Starts error monitoring in the browser, as @vigilon/browser's init() does.\n * With `withVigilon()` in next.config, Next.js renders the trace ID of the\n * request that rendered the page in a `traceparent` meta tag; every event\n * captured before the first client-side navigation carries it, so a hydration\n * or early render error links to its server trace.\n */\nexport function init(options: VigilonBrowserOptions): boolean {\n const serverTrace = readServerTrace();\n if (serverTrace === null) {\n return initBrowser(options);\n }\n const { beforeSend } = options;\n return initBrowser({\n ...options,\n beforeSend: (event) => {\n const linked = linkServerTrace(event, serverTrace);\n return beforeSend ? beforeSend(linked) : linked;\n },\n });\n}\n\ninterface ServerTrace {\n traceId: string;\n /** The page URL the server rendered, without its fragment. */\n url: string;\n}\n\nfunction readServerTrace(): ServerTrace | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const content = document.querySelector('meta[name=\"traceparent\"]')?.getAttribute(\"content\");\n // W3C traceparent: version-traceid-parentid-flags.\n const traceId = content?.split(\"-\")[1];\n if (!traceId || !/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) {\n return null;\n }\n return { traceId, url: withoutFragment(window.location.href) };\n}\n\n/**\n * The trace belongs to the request that rendered the page, so it applies only\n * while the browser is still on that page's URL.\n */\nexport function linkServerTrace(event: BrowserEvent, serverTrace: ServerTrace): BrowserEvent {\n if (event.traceId || withoutFragment(String(event.page.url)) !== serverTrace.url) {\n return event;\n }\n return { ...event, traceId: serverTrace.traceId };\n}\n\nfunction withoutFragment(url: string): string {\n const hash = url.indexOf(\"#\");\n return hash === -1 ? url : url.slice(0, hash);\n}\n","import { captureException } from \"@vigilon/react\";\nimport { useEffect } from \"react\";\n\n/**\n * Reports the error an `error.tsx` or `global-error.tsx` boundary received.\n * Next.js creates the React root itself, so @vigilon/react's\n * rootErrorHandlers cannot be passed to it; this hook reports what those\n * boundaries catch instead.\n *\n * ```tsx\n * \"use client\";\n * export default function GlobalError({ error }: { error: Error & { digest?: string } }) {\n * useReportError(error);\n * return <html><body><h2>Something went wrong.</h2></body></html>;\n * }\n * ```\n */\nexport function useReportError(error: unknown): void {\n useEffect(() => {\n captureException(error, { mechanism: \"react_error_boundary\", handled: true });\n }, [error]);\n}\n"],"mappings":";;;AAKA,cAAc;;;ACLd,SAA4B,QAAQ,mBAA+C;AAS5E,SAAS,KAAK,SAAyC;AAC5D,QAAM,cAAc,gBAAgB;AACpC,MAAI,gBAAgB,MAAM;AACxB,WAAO,YAAY,OAAO;AAAA,EAC5B;AACA,QAAM,EAAE,WAAW,IAAI;AACvB,SAAO,YAAY;AAAA,IACjB,GAAG;AAAA,IACH,YAAY,CAAC,UAAU;AACrB,YAAM,SAAS,gBAAgB,OAAO,WAAW;AACjD,aAAO,aAAa,WAAW,MAAM,IAAI;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;AAQA,SAAS,kBAAsC;AA9B/C;AA+BE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AACA,QAAM,WAAU,cAAS,cAAc,0BAA0B,MAAjD,mBAAoD,aAAa;AAEjF,QAAM,UAAU,mCAAS,MAAM,KAAK;AACpC,MAAI,CAAC,WAAW,CAAC,iBAAiB,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO,EAAE,SAAS,KAAK,gBAAgB,OAAO,SAAS,IAAI,EAAE;AAC/D;AAMO,SAAS,gBAAgB,OAAqB,aAAwC;AAC3F,MAAI,MAAM,WAAW,gBAAgB,OAAO,MAAM,KAAK,GAAG,CAAC,MAAM,YAAY,KAAK;AAChF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,GAAG,OAAO,SAAS,YAAY,QAAQ;AAClD;AAEA,SAAS,gBAAgB,KAAqB;AAC5C,QAAM,OAAO,IAAI,QAAQ,GAAG;AAC5B,SAAO,SAAS,KAAK,MAAM,IAAI,MAAM,GAAG,IAAI;AAC9C;;;ACzDA,SAAS,wBAAwB;AACjC,SAAS,iBAAiB;AAgBnB,SAAS,eAAe,OAAsB;AACnD,YAAU,MAAM;AACd,qBAAiB,OAAO,EAAE,WAAW,wBAAwB,SAAS,KAAK,CAAC;AAAA,EAC9E,GAAG,CAAC,KAAK,CAAC;AACZ;","names":[]}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/server/index.ts
|
|
32
|
+
var server_exports = {};
|
|
33
|
+
__export(server_exports, {
|
|
34
|
+
HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE: () => HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE,
|
|
35
|
+
HTTP_ROUTE_ATTRIBUTE: () => HTTP_ROUTE_ATTRIBUTE,
|
|
36
|
+
NEXT_ROUTE_ATTRIBUTE: () => NEXT_ROUTE_ATTRIBUTE,
|
|
37
|
+
NextRouteSpanProcessor: () => NextRouteSpanProcessor,
|
|
38
|
+
VercelFlushSpanProcessor: () => VercelFlushSpanProcessor,
|
|
39
|
+
VercelResponseStatusProcessor: () => VercelResponseStatusProcessor,
|
|
40
|
+
patchServerResponseStatus: () => patchServerResponseStatus,
|
|
41
|
+
register: () => register
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(server_exports);
|
|
44
|
+
__reExport(server_exports, require("@vigilon/node"), module.exports);
|
|
45
|
+
|
|
46
|
+
// src/server/register.ts
|
|
47
|
+
var import_node = require("@vigilon/node");
|
|
48
|
+
|
|
49
|
+
// src/server/nextRouteSpanProcessor.ts
|
|
50
|
+
var import_api = require("@opentelemetry/api");
|
|
51
|
+
var import_core = require("@opentelemetry/core");
|
|
52
|
+
var NEXT_ROUTE_ATTRIBUTE = "next.route";
|
|
53
|
+
var HTTP_ROUTE_ATTRIBUTE = "http.route";
|
|
54
|
+
var NEXT_SPAN_TYPE_ATTRIBUTE = "next.span_type";
|
|
55
|
+
var NEXT_ENTRY_SPAN_TYPE = "BaseServer.handleRequest";
|
|
56
|
+
var NextRouteSpanProcessor = class {
|
|
57
|
+
onStart(span, parentContext) {
|
|
58
|
+
const route = span.attributes[NEXT_ROUTE_ATTRIBUTE];
|
|
59
|
+
if (typeof route !== "string" || route.length === 0) return;
|
|
60
|
+
const rpcMetadata = (0, import_core.getRPCMetadata)(parentContext);
|
|
61
|
+
if (rpcMetadata?.type === import_core.RPCType.HTTP) {
|
|
62
|
+
rpcMetadata.route = route;
|
|
63
|
+
}
|
|
64
|
+
const parentSpan = import_api.trace.getSpan(parentContext);
|
|
65
|
+
if (!parentSpan?.isRecording()) return;
|
|
66
|
+
const parentAttributes = parentSpan.attributes;
|
|
67
|
+
if (parentAttributes?.[NEXT_SPAN_TYPE_ATTRIBUTE] === NEXT_ENTRY_SPAN_TYPE && parentAttributes[HTTP_ROUTE_ATTRIBUTE] === void 0) {
|
|
68
|
+
parentSpan.setAttribute(HTTP_ROUTE_ATTRIBUTE, route);
|
|
69
|
+
parentSpan.setAttribute(NEXT_ROUTE_ATTRIBUTE, route);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
onEnd() {
|
|
73
|
+
}
|
|
74
|
+
forceFlush() {
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
shutdown() {
|
|
78
|
+
return Promise.resolve();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/server/entrySpan.ts
|
|
83
|
+
var import_api2 = require("@opentelemetry/api");
|
|
84
|
+
function isEntrySpan(span) {
|
|
85
|
+
if (span.kind !== import_api2.SpanKind.SERVER) return false;
|
|
86
|
+
const parent = span.parentSpanContext;
|
|
87
|
+
return !parent || parent.isRemote === true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/server/vercelFlushSpanProcessor.ts
|
|
91
|
+
var VERCEL_REQUEST_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
|
|
92
|
+
var KEEP_ALIVE_TIMEOUT_MS = 15e3;
|
|
93
|
+
function vercelWaitUntil(promise) {
|
|
94
|
+
const requestContext = globalThis[VERCEL_REQUEST_CONTEXT];
|
|
95
|
+
requestContext?.get?.()?.waitUntil?.(promise);
|
|
96
|
+
}
|
|
97
|
+
var VercelFlushSpanProcessor = class {
|
|
98
|
+
constructor(flushables) {
|
|
99
|
+
this.flushables = flushables;
|
|
100
|
+
this.keepAlives = /* @__PURE__ */ new Map();
|
|
101
|
+
}
|
|
102
|
+
onStart(span, _parentContext) {
|
|
103
|
+
if (!isEntrySpan(span)) return;
|
|
104
|
+
let resolveKeepAlive;
|
|
105
|
+
const keepAlive = new Promise((resolve) => {
|
|
106
|
+
resolveKeepAlive = resolve;
|
|
107
|
+
});
|
|
108
|
+
vercelWaitUntil(keepAlive);
|
|
109
|
+
const backstop = setTimeout(resolveKeepAlive, KEEP_ALIVE_TIMEOUT_MS);
|
|
110
|
+
backstop.unref?.();
|
|
111
|
+
this.keepAlives.set(span.spanContext().spanId, () => {
|
|
112
|
+
clearTimeout(backstop);
|
|
113
|
+
resolveKeepAlive();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
onEnd(span) {
|
|
117
|
+
if (!isEntrySpan(span)) return;
|
|
118
|
+
const spanId = span.spanContext().spanId;
|
|
119
|
+
const release = this.keepAlives.get(spanId);
|
|
120
|
+
this.keepAlives.delete(spanId);
|
|
121
|
+
const exportDone = Promise.allSettled(
|
|
122
|
+
this.flushables.map((flushable) => flushable.forceFlush())
|
|
123
|
+
).then(() => void 0);
|
|
124
|
+
if (release) void exportDone.finally(release);
|
|
125
|
+
}
|
|
126
|
+
forceFlush() {
|
|
127
|
+
return Promise.resolve();
|
|
128
|
+
}
|
|
129
|
+
shutdown() {
|
|
130
|
+
for (const release of this.keepAlives.values()) release();
|
|
131
|
+
this.keepAlives.clear();
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/server/vercelResponseStatusProcessor.ts
|
|
137
|
+
var http = __toESM(require("http"));
|
|
138
|
+
var import_api3 = require("@opentelemetry/api");
|
|
139
|
+
var HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE = "http.response.status_code";
|
|
140
|
+
var VercelResponseStatusProcessor = class {
|
|
141
|
+
constructor() {
|
|
142
|
+
this.entrySpans = /* @__PURE__ */ new Map();
|
|
143
|
+
}
|
|
144
|
+
onStart(span, _parentContext) {
|
|
145
|
+
if (!isEntrySpan(span)) return;
|
|
146
|
+
this.entrySpans.set(span.spanContext().traceId, span);
|
|
147
|
+
}
|
|
148
|
+
onEnd(span) {
|
|
149
|
+
if (!isEntrySpan(span)) return;
|
|
150
|
+
this.entrySpans.delete(span.spanContext().traceId);
|
|
151
|
+
}
|
|
152
|
+
/** Called from the ServerResponse patch when a response's head is written. */
|
|
153
|
+
recordStatus(statusCode) {
|
|
154
|
+
const traceId = import_api3.trace.getActiveSpan()?.spanContext().traceId;
|
|
155
|
+
if (!traceId) return;
|
|
156
|
+
const entrySpan = this.entrySpans.get(traceId);
|
|
157
|
+
if (entrySpan?.isRecording()) {
|
|
158
|
+
entrySpan.setAttribute(HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE, statusCode);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
forceFlush() {
|
|
162
|
+
return Promise.resolve();
|
|
163
|
+
}
|
|
164
|
+
shutdown() {
|
|
165
|
+
this.entrySpans.clear();
|
|
166
|
+
return Promise.resolve();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var PATCHED_FLAG = /* @__PURE__ */ Symbol.for("vigilon.writeHead.patched");
|
|
170
|
+
function patchServerResponseStatus(processor) {
|
|
171
|
+
const prototype = http.ServerResponse.prototype;
|
|
172
|
+
if (prototype[PATCHED_FLAG]) return;
|
|
173
|
+
prototype[PATCHED_FLAG] = true;
|
|
174
|
+
const originalWriteHead = prototype.writeHead;
|
|
175
|
+
prototype.writeHead = function patchedWriteHead(...args) {
|
|
176
|
+
const statusCode = typeof args[0] === "number" ? args[0] : this.statusCode;
|
|
177
|
+
try {
|
|
178
|
+
processor.recordStatus(statusCode);
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
return originalWriteHead.apply(this, args);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/server/register.ts
|
|
186
|
+
var NODE_REGISTER_STATE_KEY = /* @__PURE__ */ Symbol.for("vigilon.register.started");
|
|
187
|
+
var NEXTJS_REGISTER_STATE_KEY = /* @__PURE__ */ Symbol.for("vigilon.register.nextjs");
|
|
188
|
+
function register(params = {}) {
|
|
189
|
+
const globalState = globalThis;
|
|
190
|
+
if (globalState[NODE_REGISTER_STATE_KEY] && !globalState[NEXTJS_REGISTER_STATE_KEY]) {
|
|
191
|
+
console.warn(
|
|
192
|
+
"Vigilon is already registered by plain @vigilon/node (most likely a NODE_OPTIONS preload of @vigilon/node/register), so the Next.js extensions (route attribution and the Vercel runtime fixes) are not active. Preload @vigilon/nextjs/register instead, or remove the preload and register from instrumentation.ts."
|
|
193
|
+
);
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
const sdk = (0, import_node.register)({
|
|
197
|
+
...params,
|
|
198
|
+
extendSpanProcessors: ({ batchSpanProcessor }) => {
|
|
199
|
+
const append = [];
|
|
200
|
+
if (process.env.VERCEL) {
|
|
201
|
+
append.push(new VercelFlushSpanProcessor([batchSpanProcessor]));
|
|
202
|
+
const responseStatusProcessor = new VercelResponseStatusProcessor();
|
|
203
|
+
append.push(responseStatusProcessor);
|
|
204
|
+
patchServerResponseStatus(responseStatusProcessor);
|
|
205
|
+
}
|
|
206
|
+
return { prepend: [new NextRouteSpanProcessor()], append };
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
globalState[NEXTJS_REGISTER_STATE_KEY] = true;
|
|
210
|
+
return sdk;
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE,
|
|
215
|
+
HTTP_ROUTE_ATTRIBUTE,
|
|
216
|
+
NEXT_ROUTE_ATTRIBUTE,
|
|
217
|
+
NextRouteSpanProcessor,
|
|
218
|
+
VercelFlushSpanProcessor,
|
|
219
|
+
VercelResponseStatusProcessor,
|
|
220
|
+
patchServerResponseStatus,
|
|
221
|
+
register,
|
|
222
|
+
...require("@vigilon/node")
|
|
223
|
+
});
|
|
224
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/index.ts","../src/server/register.ts","../src/server/nextRouteSpanProcessor.ts","../src/server/entrySpan.ts","../src/server/vercelFlushSpanProcessor.ts","../src/server/vercelResponseStatusProcessor.ts"],"sourcesContent":["/**\n * @vigilon/nextjs/server: the Node.js runtime half of @vigilon/nextjs, for\n * instrumentation.ts. Re-exports @vigilon/node (recordException,\n * withJobMonitor, and the rest), with a Next.js-aware register().\n */\nexport * from \"@vigilon/node\";\nexport { register, type VigilonNextjsRegisterParams } from \"./register\";\nexport {\n HTTP_ROUTE_ATTRIBUTE,\n NEXT_ROUTE_ATTRIBUTE,\n NextRouteSpanProcessor,\n} from \"./nextRouteSpanProcessor\";\nexport { type Flushable, VercelFlushSpanProcessor } from \"./vercelFlushSpanProcessor\";\nexport {\n HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE,\n patchServerResponseStatus,\n VercelResponseStatusProcessor,\n} from \"./vercelResponseStatusProcessor\";\n","import type { SpanProcessor } from \"@opentelemetry/sdk-trace-node\";\nimport { register as registerNode, type VigilonRegisterParams } from \"@vigilon/node\";\nimport { NextRouteSpanProcessor } from \"./nextRouteSpanProcessor\";\nimport { VercelFlushSpanProcessor } from \"./vercelFlushSpanProcessor\";\nimport {\n patchServerResponseStatus,\n VercelResponseStatusProcessor,\n} from \"./vercelResponseStatusProcessor\";\n\nexport type VigilonNextjsRegisterParams = Omit<VigilonRegisterParams, \"extendSpanProcessors\">;\n\n// Shared with @vigilon/node: set once any register() variant has started an\n// SDK in this process.\nconst NODE_REGISTER_STATE_KEY = Symbol.for(\"vigilon.register.started\");\n// Set only by this register(), so a plain @vigilon/node start followed by\n// this call is told apart from a harmless second call of this one.\nconst NEXTJS_REGISTER_STATE_KEY = Symbol.for(\"vigilon.register.nextjs\");\n\n/**\n * Starts Vigilon in the Next.js Node.js runtime: @vigilon/node's register()\n * plus route attribution from Next's own spans and, on Vercel, the\n * end-of-request flush and real response status. Call it from\n * instrumentation.ts. Every parameter is optional, as with @vigilon/node.\n */\nexport function register(\n params: VigilonNextjsRegisterParams = {},\n): ReturnType<typeof registerNode> {\n const globalState = globalThis as Record<symbol, unknown>;\n if (globalState[NODE_REGISTER_STATE_KEY] && !globalState[NEXTJS_REGISTER_STATE_KEY]) {\n console.warn(\n \"Vigilon is already registered by plain @vigilon/node (most likely a NODE_OPTIONS preload of @vigilon/node/register), so the Next.js extensions (route attribution and the Vercel runtime fixes) are not active. Preload @vigilon/nextjs/register instead, or remove the preload and register from instrumentation.ts.\",\n );\n return undefined;\n }\n const sdk = registerNode({\n ...params,\n extendSpanProcessors: ({ batchSpanProcessor }) => {\n const append: SpanProcessor[] = [];\n // Vercel suspends the instance as soon as the response is sent, which\n // stops the batch and export timers, so telemetry would leave late or\n // not at all. Flush when each entry span ends instead. Must come after\n // the batch processor so the ending span is already enqueued.\n if (process.env.VERCEL) {\n append.push(new VercelFlushSpanProcessor([batchSpanProcessor]));\n // Vercel's bundled Next.js runtime stamps the entry span's status\n // before the response is written (always 200), so the real status is\n // recorded from ServerResponse.writeHead instead.\n const responseStatusProcessor = new VercelResponseStatusProcessor();\n append.push(responseStatusProcessor);\n patchServerResponseStatus(responseStatusProcessor);\n }\n return { prepend: [new NextRouteSpanProcessor()], append };\n },\n });\n globalState[NEXTJS_REGISTER_STATE_KEY] = true;\n return sdk;\n}\n","import { Context, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, RPCType } from \"@opentelemetry/core\";\nimport {\n SpanProcessor,\n Span,\n ReadableSpan,\n} from \"@opentelemetry/sdk-trace-node\";\n\nexport const NEXT_ROUTE_ATTRIBUTE = \"next.route\";\nexport const HTTP_ROUTE_ATTRIBUTE = \"http.route\";\n\nconst NEXT_SPAN_TYPE_ATTRIBUTE = \"next.span_type\";\nconst NEXT_ENTRY_SPAN_TYPE = \"BaseServer.handleRequest\";\n\n/**\n * Propagates the route Next.js resolves to the two places the rest of the\n * pipeline reads it from, neither of which Next.js reliably fills itself:\n *\n * 1. The incoming request's RPC metadata — where the HTTP instrumentation\n * reads `http.route` when labelling its server span. Express and Fastify\n * instrumentations write the matched route there; Next.js never does.\n *\n * 2. Next's own `BaseServer.handleRequest` entry span. Next normally copies\n * `next.route` onto it via a tracer-internal root-span-attribute store,\n * but bundled runtimes (Vercel's serverless build) load private copies of\n * Next's tracer per route chunk, so the route module writes to one store\n * while the base server reads another and the entry span exports without\n * a route. Endpoint metrics are collector-derived from entry spans, so\n * every Vercel request was bucketed as \"unknown\".\n *\n * A span processor is used instead of patching Next's internals because the\n * global tracer provider is the one channel every bundled copy of Next's\n * tracer shares — proven by child spans parenting correctly even where the\n * attribute store is split.\n */\nexport class NextRouteSpanProcessor implements SpanProcessor {\n onStart(span: Span, parentContext: Context): void {\n const route = span.attributes[NEXT_ROUTE_ATTRIBUTE];\n if (typeof route !== \"string\" || route.length === 0) return;\n\n const rpcMetadata = getRPCMetadata(parentContext);\n if (rpcMetadata?.type === RPCType.HTTP) {\n rpcMetadata.route = route;\n }\n\n const parentSpan = trace.getSpan(parentContext);\n if (!parentSpan?.isRecording()) return;\n // Only SDK spans expose attributes; a NonRecordingSpan (remote or no-op\n // parent) has none and is skipped by the checks below.\n const parentAttributes = (parentSpan as unknown as ReadableSpan).attributes;\n if (\n parentAttributes?.[NEXT_SPAN_TYPE_ATTRIBUTE] === NEXT_ENTRY_SPAN_TYPE &&\n parentAttributes[HTTP_ROUTE_ATTRIBUTE] === undefined\n ) {\n parentSpan.setAttribute(HTTP_ROUTE_ATTRIBUTE, route);\n parentSpan.setAttribute(NEXT_ROUTE_ATTRIBUTE, route);\n }\n }\n\n onEnd(): void {}\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n}\n","import { SpanKind } from \"@opentelemetry/api\";\nimport { ReadableSpan, Span } from \"@opentelemetry/sdk-trace-node\";\n\n/**\n * A service-entry span: the one SERVER span that marks a request entering\n * this service — its parent is either absent or remote. Matches the\n * definition the collector counts for endpoint metrics.\n */\nexport function isEntrySpan(span: Span | ReadableSpan): boolean {\n if (span.kind !== SpanKind.SERVER) return false;\n const parent = (span as ReadableSpan).parentSpanContext;\n return !parent || parent.isRemote === true;\n}\n","import { Context } from \"@opentelemetry/api\";\nimport { SpanProcessor, Span, ReadableSpan } from \"@opentelemetry/sdk-trace-node\";\nimport { isEntrySpan } from \"./entrySpan\";\n\nexport type Flushable = { forceFlush(): Promise<void> };\n\n// The request-context global that @vercel/functions uses to reach waitUntil.\n// Reading it directly keeps the SDK dependency-free; it is simply absent\n// outside Vercel's runtime.\nconst VERCEL_REQUEST_CONTEXT = Symbol.for(\"@vercel/request-context\");\n\n// Backstop so a request that never ends its entry span (crash, client\n// disconnect edge cases) cannot pin the instance — and its billing — open\n// indefinitely via the keep-alive registered at span start.\nconst KEEP_ALIVE_TIMEOUT_MS = 15_000;\n\nfunction vercelWaitUntil(promise: Promise<unknown>): void {\n const requestContext = (\n globalThis as Record<symbol, unknown> as {\n [VERCEL_REQUEST_CONTEXT]?: {\n get?: () => { waitUntil?: (p: Promise<unknown>) => void } | undefined;\n };\n }\n )[VERCEL_REQUEST_CONTEXT];\n requestContext?.get?.()?.waitUntil?.(promise);\n}\n\n/**\n * Exports telemetry at the end of every request instead of trusting timers.\n *\n * Vercel suspends the function instance the moment a response is sent, so the\n * BatchSpanProcessor's export timer rarely gets to run, and even an export\n * started at span end freezes mid-POST: it only completes if another request\n * thaws the instance before the connection dies.\n * Observed in production as \"N consecutive requests record N-1\", \"spaced-out\n * requests record nothing\", and stale spans surfacing alongside later\n * traffic.\n *\n * The keep-alive is therefore registered with Vercel's waitUntil when the\n * entry span STARTS — registrations made after the response settles are\n * silently dropped, and the entry span only ends after that point. The\n * deferred handed to waitUntil at start is resolved once the end-of-request\n * flush completes, so the instance stays alive exactly long enough for the\n * export POST to land.\n *\n * Must be registered AFTER the BatchSpanProcessor: processors run in order,\n * and the entry span must already be enqueued when the flush starts.\n */\nexport class VercelFlushSpanProcessor implements SpanProcessor {\n private readonly keepAlives = new Map<string, () => void>();\n\n constructor(private readonly flushables: Flushable[]) {}\n\n onStart(span: Span, _parentContext: Context): void {\n if (!isEntrySpan(span)) return;\n\n let resolveKeepAlive!: () => void;\n const keepAlive = new Promise<void>((resolve) => {\n resolveKeepAlive = resolve;\n });\n vercelWaitUntil(keepAlive);\n\n const backstop = setTimeout(resolveKeepAlive, KEEP_ALIVE_TIMEOUT_MS);\n backstop.unref?.();\n this.keepAlives.set(span.spanContext().spanId, () => {\n clearTimeout(backstop);\n resolveKeepAlive();\n });\n }\n\n onEnd(span: ReadableSpan): void {\n if (!isEntrySpan(span)) return;\n\n const spanId = span.spanContext().spanId;\n const release = this.keepAlives.get(spanId);\n this.keepAlives.delete(spanId);\n\n const exportDone = Promise.allSettled(\n this.flushables.map((flushable) => flushable.forceFlush()),\n ).then(() => undefined);\n if (release) void exportDone.finally(release);\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n for (const release of this.keepAlives.values()) release();\n this.keepAlives.clear();\n return Promise.resolve();\n }\n}\n","import * as http from \"node:http\";\nimport { Context, trace } from \"@opentelemetry/api\";\nimport {\n SpanProcessor,\n Span,\n ReadableSpan,\n} from \"@opentelemetry/sdk-trace-node\";\nimport { isEntrySpan } from \"./entrySpan\";\n\nexport const HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE = \"http.response.status_code\";\n\n/**\n * Records the real HTTP response status onto the service-entry span on Vercel.\n *\n * Vercel's bundled Next.js runtime stamps the entry span's status from the\n * response object before the handler's response is written, so the span always\n * reports the default 200 — a 429 or 500 to the client still exports as 200,\n * and warn/error rates flatline. The truth passes through\n * `ServerResponse.prototype.writeHead` when the response is actually written,\n * so this processor tracks live entry spans by trace id and a prototype patch\n * (see `patchServerResponseStatus`) reports the written status back to the\n * entry span of whichever trace is active.\n *\n * The stable `http.response.status_code` key is set deliberately: the bundled\n * runtime writes only the old `http.status_code` key, and the collector only\n * normalizes old → stable when the stable key is absent, so the value recorded\n * here survives regardless of what the runtime writes afterwards.\n */\nexport class VercelResponseStatusProcessor implements SpanProcessor {\n private readonly entrySpans = new Map<string, Span>();\n\n onStart(span: Span, _parentContext: Context): void {\n if (!isEntrySpan(span)) return;\n this.entrySpans.set(span.spanContext().traceId, span);\n }\n\n onEnd(span: ReadableSpan): void {\n if (!isEntrySpan(span)) return;\n this.entrySpans.delete(span.spanContext().traceId);\n }\n\n /** Called from the ServerResponse patch when a response's head is written. */\n recordStatus(statusCode: number): void {\n const traceId = trace.getActiveSpan()?.spanContext().traceId;\n if (!traceId) return;\n const entrySpan = this.entrySpans.get(traceId);\n if (entrySpan?.isRecording()) {\n entrySpan.setAttribute(HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE, statusCode);\n }\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n this.entrySpans.clear();\n return Promise.resolve();\n }\n}\n\nconst PATCHED_FLAG = Symbol.for(\"vigilon.writeHead.patched\");\n\n/**\n * Patches `ServerResponse.prototype.writeHead` to report the written status to\n * the processor. Prototype patching works regardless of when the server was\n * created, which is what makes this viable on Vercel where the runtime's\n * server exists before the SDK starts (the reason HttpInstrumentation never\n * engages there). Node's implicit header write on `res.end()` also goes\n * through `writeHead`, so unwritten-head responses are covered too.\n */\nexport function patchServerResponseStatus(\n processor: VercelResponseStatusProcessor,\n): void {\n const prototype = http.ServerResponse.prototype as typeof http.ServerResponse.prototype & {\n [PATCHED_FLAG]?: boolean;\n };\n if (prototype[PATCHED_FLAG]) return;\n prototype[PATCHED_FLAG] = true;\n\n const originalWriteHead = prototype.writeHead;\n prototype.writeHead = function patchedWriteHead(\n this: http.ServerResponse,\n ...args: Parameters<typeof originalWriteHead>\n ) {\n const statusCode = typeof args[0] === \"number\" ? args[0] : this.statusCode;\n try {\n processor.recordStatus(statusCode);\n } catch {\n // Never let telemetry break the response path.\n }\n return originalWriteHead.apply(this, args);\n } as typeof originalWriteHead;\n}\n"],"mappings":"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","names":["import_api","import_api","registerNode"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { VigilonRegisterParams, register as register$1 } from '@vigilon/node';
|
|
2
|
+
export * from '@vigilon/node';
|
|
3
|
+
import { Context } from '@opentelemetry/api';
|
|
4
|
+
import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-node';
|
|
5
|
+
|
|
6
|
+
type VigilonNextjsRegisterParams = Omit<VigilonRegisterParams, "extendSpanProcessors">;
|
|
7
|
+
/**
|
|
8
|
+
* Starts Vigilon in the Next.js Node.js runtime: @vigilon/node's register()
|
|
9
|
+
* plus route attribution from Next's own spans and, on Vercel, the
|
|
10
|
+
* end-of-request flush and real response status. Call it from
|
|
11
|
+
* instrumentation.ts. Every parameter is optional, as with @vigilon/node.
|
|
12
|
+
*/
|
|
13
|
+
declare function register(params?: VigilonNextjsRegisterParams): ReturnType<typeof register$1>;
|
|
14
|
+
|
|
15
|
+
declare const NEXT_ROUTE_ATTRIBUTE = "next.route";
|
|
16
|
+
declare const HTTP_ROUTE_ATTRIBUTE = "http.route";
|
|
17
|
+
/**
|
|
18
|
+
* Propagates the route Next.js resolves to the two places the rest of the
|
|
19
|
+
* pipeline reads it from, neither of which Next.js reliably fills itself:
|
|
20
|
+
*
|
|
21
|
+
* 1. The incoming request's RPC metadata — where the HTTP instrumentation
|
|
22
|
+
* reads `http.route` when labelling its server span. Express and Fastify
|
|
23
|
+
* instrumentations write the matched route there; Next.js never does.
|
|
24
|
+
*
|
|
25
|
+
* 2. Next's own `BaseServer.handleRequest` entry span. Next normally copies
|
|
26
|
+
* `next.route` onto it via a tracer-internal root-span-attribute store,
|
|
27
|
+
* but bundled runtimes (Vercel's serverless build) load private copies of
|
|
28
|
+
* Next's tracer per route chunk, so the route module writes to one store
|
|
29
|
+
* while the base server reads another and the entry span exports without
|
|
30
|
+
* a route. Endpoint metrics are collector-derived from entry spans, so
|
|
31
|
+
* every Vercel request was bucketed as "unknown".
|
|
32
|
+
*
|
|
33
|
+
* A span processor is used instead of patching Next's internals because the
|
|
34
|
+
* global tracer provider is the one channel every bundled copy of Next's
|
|
35
|
+
* tracer shares — proven by child spans parenting correctly even where the
|
|
36
|
+
* attribute store is split.
|
|
37
|
+
*/
|
|
38
|
+
declare class NextRouteSpanProcessor implements SpanProcessor {
|
|
39
|
+
onStart(span: Span, parentContext: Context): void;
|
|
40
|
+
onEnd(): void;
|
|
41
|
+
forceFlush(): Promise<void>;
|
|
42
|
+
shutdown(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Flushable = {
|
|
46
|
+
forceFlush(): Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Exports telemetry at the end of every request instead of trusting timers.
|
|
50
|
+
*
|
|
51
|
+
* Vercel suspends the function instance the moment a response is sent, so the
|
|
52
|
+
* BatchSpanProcessor's export timer rarely gets to run, and even an export
|
|
53
|
+
* started at span end freezes mid-POST: it only completes if another request
|
|
54
|
+
* thaws the instance before the connection dies.
|
|
55
|
+
* Observed in production as "N consecutive requests record N-1", "spaced-out
|
|
56
|
+
* requests record nothing", and stale spans surfacing alongside later
|
|
57
|
+
* traffic.
|
|
58
|
+
*
|
|
59
|
+
* The keep-alive is therefore registered with Vercel's waitUntil when the
|
|
60
|
+
* entry span STARTS — registrations made after the response settles are
|
|
61
|
+
* silently dropped, and the entry span only ends after that point. The
|
|
62
|
+
* deferred handed to waitUntil at start is resolved once the end-of-request
|
|
63
|
+
* flush completes, so the instance stays alive exactly long enough for the
|
|
64
|
+
* export POST to land.
|
|
65
|
+
*
|
|
66
|
+
* Must be registered AFTER the BatchSpanProcessor: processors run in order,
|
|
67
|
+
* and the entry span must already be enqueued when the flush starts.
|
|
68
|
+
*/
|
|
69
|
+
declare class VercelFlushSpanProcessor implements SpanProcessor {
|
|
70
|
+
private readonly flushables;
|
|
71
|
+
private readonly keepAlives;
|
|
72
|
+
constructor(flushables: Flushable[]);
|
|
73
|
+
onStart(span: Span, _parentContext: Context): void;
|
|
74
|
+
onEnd(span: ReadableSpan): void;
|
|
75
|
+
forceFlush(): Promise<void>;
|
|
76
|
+
shutdown(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare const HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE = "http.response.status_code";
|
|
80
|
+
/**
|
|
81
|
+
* Records the real HTTP response status onto the service-entry span on Vercel.
|
|
82
|
+
*
|
|
83
|
+
* Vercel's bundled Next.js runtime stamps the entry span's status from the
|
|
84
|
+
* response object before the handler's response is written, so the span always
|
|
85
|
+
* reports the default 200 — a 429 or 500 to the client still exports as 200,
|
|
86
|
+
* and warn/error rates flatline. The truth passes through
|
|
87
|
+
* `ServerResponse.prototype.writeHead` when the response is actually written,
|
|
88
|
+
* so this processor tracks live entry spans by trace id and a prototype patch
|
|
89
|
+
* (see `patchServerResponseStatus`) reports the written status back to the
|
|
90
|
+
* entry span of whichever trace is active.
|
|
91
|
+
*
|
|
92
|
+
* The stable `http.response.status_code` key is set deliberately: the bundled
|
|
93
|
+
* runtime writes only the old `http.status_code` key, and the collector only
|
|
94
|
+
* normalizes old → stable when the stable key is absent, so the value recorded
|
|
95
|
+
* here survives regardless of what the runtime writes afterwards.
|
|
96
|
+
*/
|
|
97
|
+
declare class VercelResponseStatusProcessor implements SpanProcessor {
|
|
98
|
+
private readonly entrySpans;
|
|
99
|
+
onStart(span: Span, _parentContext: Context): void;
|
|
100
|
+
onEnd(span: ReadableSpan): void;
|
|
101
|
+
/** Called from the ServerResponse patch when a response's head is written. */
|
|
102
|
+
recordStatus(statusCode: number): void;
|
|
103
|
+
forceFlush(): Promise<void>;
|
|
104
|
+
shutdown(): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Patches `ServerResponse.prototype.writeHead` to report the written status to
|
|
108
|
+
* the processor. Prototype patching works regardless of when the server was
|
|
109
|
+
* created, which is what makes this viable on Vercel where the runtime's
|
|
110
|
+
* server exists before the SDK starts (the reason HttpInstrumentation never
|
|
111
|
+
* engages there). Node's implicit header write on `res.end()` also goes
|
|
112
|
+
* through `writeHead`, so unwritten-head responses are covered too.
|
|
113
|
+
*/
|
|
114
|
+
declare function patchServerResponseStatus(processor: VercelResponseStatusProcessor): void;
|
|
115
|
+
|
|
116
|
+
export { type Flushable, HTTP_RESPONSE_STATUS_CODE_ATTRIBUTE, HTTP_ROUTE_ATTRIBUTE, NEXT_ROUTE_ATTRIBUTE, NextRouteSpanProcessor, VercelFlushSpanProcessor, VercelResponseStatusProcessor, type VigilonNextjsRegisterParams, patchServerResponseStatus, register };
|