@devkitio/faultlens 0.1.5

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.
@@ -0,0 +1,40 @@
1
+ // src/vue/index.ts
2
+ function componentName(instance) {
3
+ if (typeof instance !== "object" || instance === null || !("$options" in instance)) return void 0;
4
+ const options = instance.$options;
5
+ return typeof options?.name === "string" ? options.name : void 0;
6
+ }
7
+ function installVueErrorMonitor(app, monitor) {
8
+ const original = app.config.errorHandler;
9
+ const wrapped = (error, instance, info) => {
10
+ try {
11
+ return original?.(error, instance, info);
12
+ } finally {
13
+ queueMicrotask(() => {
14
+ try {
15
+ monitor.captureException(error, {
16
+ handled: false,
17
+ mechanism: "vue.errorHandler",
18
+ context: { info, component: componentName(instance) }
19
+ });
20
+ } catch {
21
+ }
22
+ });
23
+ }
24
+ };
25
+ app.config.errorHandler = wrapped;
26
+ return () => {
27
+ if (app.config.errorHandler !== wrapped) return;
28
+ if (original) app.config.errorHandler = original;
29
+ else delete app.config.errorHandler;
30
+ };
31
+ }
32
+ function createVueErrorMonitorPlugin(monitor) {
33
+ return {
34
+ install(app) {
35
+ installVueErrorMonitor(app, monitor);
36
+ }
37
+ };
38
+ }
39
+
40
+ export { createVueErrorMonitorPlugin, installVueErrorMonitor };
@@ -0,0 +1,30 @@
1
+ import { createHash, createHmac } from 'crypto';
2
+
3
+ // src/nuxt/server-transport.ts
4
+ function createSignedRelayHeaders(config, body, eventId, timestamp = Math.floor(Date.now() / 1e3)) {
5
+ const url = new URL(config.upstream);
6
+ if (url.protocol !== "https:" && url.hostname !== "localhost") {
7
+ throw new Error("Relay \u4E0A\u6E38\u5FC5\u987B\u4F7F\u7528 HTTPS");
8
+ }
9
+ const bodySha256 = createHash("sha256").update(body).digest("hex");
10
+ const canonical = [
11
+ "POST",
12
+ url.pathname,
13
+ String(timestamp),
14
+ eventId,
15
+ bodySha256,
16
+ config.relayKeyVersion
17
+ ].join("\n");
18
+ const signature = createHmac("sha256", config.relaySecret).update(canonical).digest("hex");
19
+ return {
20
+ "content-type": "application/json",
21
+ "x-faultlens-key-id": config.relayKeyId,
22
+ "x-faultlens-key-version": config.relayKeyVersion,
23
+ "x-faultlens-event-id": eventId,
24
+ "x-faultlens-timestamp": String(timestamp),
25
+ "x-faultlens-body-sha256": bodySha256,
26
+ "x-faultlens-signature": signature
27
+ };
28
+ }
29
+
30
+ export { createSignedRelayHeaders };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ import { lstat, readdir, readFile, writeFile, unlink } from 'fs/promises';
3
+ import path from 'path';
4
+ import { randomUUID, createHash } from 'crypto';
5
+
6
+ var DEBUG_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
+ var SOURCE_MAP_REFERENCE_PATTERN = /(?:\/\*[#@]\s*sourceMappingURL=.*?\*\/|\/\/[#@]\s*sourceMappingURL=.*?$)/gm;
8
+ var DEBUG_ID_COMMENT_PATTERN = /^\/\/#\s*debugId=[0-9a-f-]{36}\s*$/gim;
9
+ var FAULTLENS_INJECTION_PATTERN = /\n?;\(\(\)=>\{try\{const g=globalThis,s=new Error\(\)\.stack;if\(s\)\{\(g\.__FAULTLENS_DEBUG_IDS__\?\?=\{\}\)\[s\]="[0-9a-f-]{36}"\}\}catch\{\}\}\)\(\);?/g;
10
+ function normalizeSourceMapPath(value) {
11
+ const posix = value.split(path.sep).join("/");
12
+ if (!posix || posix.includes("\\") || posix.startsWith("/") || /^[a-zA-Z]:/.test(posix)) {
13
+ throw new Error(`\u5236\u54C1\u8DEF\u5F84\u65E0\u6548\uFF1A${value}`);
14
+ }
15
+ if (posix.split("/").some((part) => part === ".." || part === ".")) {
16
+ throw new Error(`\u5236\u54C1\u8DEF\u5F84\u5305\u542B\u76EE\u5F55\u7A7F\u8D8A\uFF1A${value}`);
17
+ }
18
+ if (path.posix.normalize(posix) !== posix) throw new Error(`\u5236\u54C1\u8DEF\u5F84\u672A\u89C4\u8303\u5316\uFF1A${value}`);
19
+ return posix;
20
+ }
21
+ async function sourceMapFiles(root, current = root) {
22
+ const entries = await readdir(current, { withFileTypes: true });
23
+ const result = [];
24
+ for (const entry of entries) {
25
+ const absolute = path.join(current, entry.name);
26
+ const stats = await lstat(absolute);
27
+ if (stats.isSymbolicLink()) throw new Error(`\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\uFF1A${absolute}`);
28
+ if (stats.isDirectory()) result.push(...await sourceMapFiles(root, absolute));
29
+ else if (stats.isFile() && entry.name.endsWith(".map")) result.push(absolute);
30
+ }
31
+ return result.sort();
32
+ }
33
+ function runtimeInjection(debugId) {
34
+ return `;(()=>{try{const g=globalThis,s=new Error().stack;if(s){(g.__FAULTLENS_DEBUG_IDS__??={})[s]="${debugId}"}}catch{}})();
35
+ //# debugId=${debugId}`;
36
+ }
37
+ function correspondingBundle(root, mapPath, document) {
38
+ const candidate = typeof document.file === "string" && document.file ? path.resolve(path.dirname(mapPath), document.file) : mapPath.slice(0, -4);
39
+ const resolvedRoot = path.resolve(root);
40
+ const resolvedCandidate = path.resolve(candidate);
41
+ if (!resolvedCandidate.startsWith(`${resolvedRoot}${path.sep}`)) {
42
+ throw new Error(`Source Map \u6307\u5411\u6839\u76EE\u5F55\u4E4B\u5916\u7684\u8FD0\u884C\u5236\u54C1\uFF1A${mapPath}`);
43
+ }
44
+ return resolvedCandidate;
45
+ }
46
+ async function prepareOne(root, absolutePath, dryRun) {
47
+ const originalBody = await readFile(absolutePath);
48
+ const relativePath = normalizeSourceMapPath(path.relative(root, absolutePath));
49
+ let document;
50
+ try {
51
+ document = JSON.parse(originalBody.toString("utf8"));
52
+ } catch {
53
+ throw new Error(`Source Map JSON \u65E0\u6548\uFF1A${relativePath}`);
54
+ }
55
+ if (document.version !== 3) throw new Error(`Source Map \u7248\u672C\u4E0D\u53D7\u652F\u6301\uFF1A${relativePath}`);
56
+ const existingDebugId = document.debug_id ?? document.debugId;
57
+ if (existingDebugId !== void 0 && (typeof existingDebugId !== "string" || !DEBUG_ID_PATTERN.test(existingDebugId))) {
58
+ throw new Error(`Source Map Debug ID \u65E0\u6548\uFF1A${relativePath}`);
59
+ }
60
+ const debugId = typeof existingDebugId === "string" ? existingDebugId : randomUUID();
61
+ document.debug_id = debugId;
62
+ delete document.debugId;
63
+ const body = Buffer.from(JSON.stringify(document), "utf8");
64
+ const bundlePath = correspondingBundle(root, absolutePath, document);
65
+ const bundleStats = await lstat(bundlePath).catch(() => void 0);
66
+ if (!bundleStats?.isFile() || bundleStats.isSymbolicLink()) {
67
+ throw new Error(`Source Map \u7F3A\u5C11\u5B89\u5168\u7684\u5BF9\u5E94\u8FD0\u884C\u5236\u54C1\uFF1A${relativePath}`);
68
+ }
69
+ const originalBundle = await readFile(bundlePath, "utf8");
70
+ const cleanedBundle = originalBundle.replace(SOURCE_MAP_REFERENCE_PATTERN, "").replace(FAULTLENS_INJECTION_PATTERN, "").replace(DEBUG_ID_COMMENT_PATTERN, "").trimEnd();
71
+ const preparedBundle = `${cleanedBundle}
72
+ ${runtimeInjection(debugId)}
73
+ `;
74
+ if (!dryRun) {
75
+ await writeFile(absolutePath, body, { mode: 384 });
76
+ await writeFile(bundlePath, preparedBundle, { mode: 384 });
77
+ }
78
+ return {
79
+ absolutePath,
80
+ relativePath,
81
+ sha256: createHash("sha256").update(body).digest("hex"),
82
+ size: body.byteLength,
83
+ debugId,
84
+ body
85
+ };
86
+ }
87
+ async function prepareSourceMapArtifacts(root, dryRun) {
88
+ const resolvedRoot = path.resolve(root);
89
+ const rootStats = await lstat(resolvedRoot);
90
+ if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error("--root \u5FC5\u987B\u662F\u771F\u5B9E\u76EE\u5F55");
91
+ return Promise.all((await sourceMapFiles(resolvedRoot)).map((file) => prepareOne(resolvedRoot, file, dryRun)));
92
+ }
93
+ async function removeUploadedSourceMaps(artifacts) {
94
+ for (const artifact of artifacts) await unlink(artifact.absolutePath);
95
+ }
96
+
97
+ // src/cli/index.ts
98
+ function argument(name) {
99
+ const index = process.argv.indexOf(name);
100
+ return index >= 0 ? process.argv[index + 1] : void 0;
101
+ }
102
+ function required(value, label) {
103
+ if (!value) throw new Error(`\u7F3A\u5C11 ${label}`);
104
+ return value;
105
+ }
106
+ function environment(name, legacyName) {
107
+ return process.env[name] ?? process.env[legacyName];
108
+ }
109
+ function options() {
110
+ return {
111
+ endpoint: required(argument("--endpoint") ?? environment("FAULTLENS_SOURCEMAP_ENDPOINT", "MOQU_SOURCEMAP_ENDPOINT"), "--endpoint"),
112
+ token: required(argument("--token") ?? environment("FAULTLENS_SOURCEMAP_TOKEN", "MOQU_SOURCEMAP_TOKEN"), "--token \u6216 FAULTLENS_SOURCEMAP_TOKEN"),
113
+ root: path.resolve(required(argument("--root") ?? environment("FAULTLENS_SOURCEMAP_ROOT", "MOQU_SOURCEMAP_ROOT"), "--root \u6216 FAULTLENS_SOURCEMAP_ROOT")),
114
+ release: required(argument("--release") ?? environment("FAULTLENS_RELEASE", "MOQU_RELEASE"), "--release \u6216 FAULTLENS_RELEASE"),
115
+ environment: required(
116
+ argument("--environment") ?? environment("FAULTLENS_ENVIRONMENT", "MOQU_ENVIRONMENT"),
117
+ "--environment \u6216 FAULTLENS_ENVIRONMENT"
118
+ ),
119
+ dist: argument("--dist") ?? environment("FAULTLENS_DIST", "MOQU_DIST") ?? "default",
120
+ dryRun: process.argv.includes("--dry-run"),
121
+ deleteAfterVerify: process.argv.includes("--delete-after-verify")
122
+ };
123
+ }
124
+ async function upload(config, artifacts) {
125
+ if (config.dryRun) {
126
+ console.info(`\u6F14\u7EC3\u5B8C\u6210\uFF1A\u5C06\u4E0A\u4F20 ${artifacts.length} \u4E2A Source Map \u6587\u4EF6`);
127
+ return;
128
+ }
129
+ for (const current of artifacts) {
130
+ const response = await fetch(`${config.endpoint.replace(/\/$/, "")}/v1/admin/source-maps/artifacts`, {
131
+ method: "PUT",
132
+ headers: {
133
+ authorization: `Bearer ${config.token}`,
134
+ "content-type": "application/octet-stream",
135
+ "x-faultlens-release": config.release,
136
+ "x-faultlens-environment": config.environment,
137
+ "x-faultlens-dist": config.dist,
138
+ "x-faultlens-artifact-path": current.relativePath,
139
+ "x-faultlens-debug-id": current.debugId,
140
+ "x-faultlens-sha256": current.sha256,
141
+ "x-faultlens-size": String(current.size)
142
+ },
143
+ body: current.body.buffer.slice(
144
+ current.body.byteOffset,
145
+ current.body.byteOffset + current.body.byteLength
146
+ ),
147
+ redirect: "error"
148
+ });
149
+ if (!response.ok) throw new Error(`\u4E0A\u4F20\u5931\u8D25\uFF1A${current.relativePath}\uFF0C\u72B6\u6001\u7801 ${response.status}`);
150
+ }
151
+ console.info(`\u4E0A\u4F20\u5B8C\u6210\uFF1A${artifacts.length} \u4E2A Source Map \u6587\u4EF6`);
152
+ }
153
+ async function verify(config, artifacts) {
154
+ const response = await fetch(`${config.endpoint.replace(/\/$/, "")}/v1/admin/source-maps/verify`, {
155
+ method: "POST",
156
+ headers: {
157
+ authorization: `Bearer ${config.token}`,
158
+ "content-type": "application/json"
159
+ },
160
+ body: JSON.stringify({
161
+ release: config.release,
162
+ environment: config.environment,
163
+ dist: config.dist,
164
+ artifacts: artifacts.map(({ relativePath, sha256, size, debugId }) => ({
165
+ path: relativePath,
166
+ sha256,
167
+ size,
168
+ debugId
169
+ }))
170
+ }),
171
+ redirect: "error"
172
+ });
173
+ if (!response.ok) throw new Error(`\u5236\u54C1\u6E05\u5355\u6821\u9A8C\u5931\u8D25\uFF0C\u72B6\u6001\u7801 ${response.status}`);
174
+ if (config.deleteAfterVerify) await removeUploadedSourceMaps(artifacts);
175
+ console.info("Source Map \u5236\u54C1\u6E05\u5355\u4E0E\u670D\u52A1\u7AEF\u4E00\u81F4");
176
+ }
177
+ async function doctor(config) {
178
+ const url = new URL(config.endpoint);
179
+ if (url.protocol !== "https:" && url.hostname !== "localhost") throw new Error("\u670D\u52A1\u5730\u5740\u5FC5\u987B\u4F7F\u7528 HTTPS");
180
+ if (process.versions.node.split(".")[0] !== "22") {
181
+ console.warn(`\u5F53\u524D Node.js \u4E3A ${process.version}\uFF0C\u751F\u4EA7 CI \u5E94\u4F7F\u7528 Node.js 22`);
182
+ }
183
+ await lstat(config.root);
184
+ console.info("CLI \u914D\u7F6E\u68C0\u67E5\u901A\u8FC7");
185
+ }
186
+ async function main() {
187
+ const command = process.argv[2];
188
+ const subcommand = process.argv[3];
189
+ const config = options();
190
+ if (command === "doctor") return doctor(config);
191
+ if (command !== "sourcemaps" || !["upload", "verify"].includes(subcommand ?? "")) {
192
+ throw new Error("\u7528\u6CD5\uFF1Afaultlens sourcemaps <upload|verify> [\u53C2\u6570]\uFF0C\u6216 faultlens doctor [\u53C2\u6570]");
193
+ }
194
+ const artifacts = await prepareSourceMapArtifacts(config.root, config.dryRun);
195
+ if (subcommand === "upload") return upload(config, artifacts);
196
+ if (config.dryRun) {
197
+ console.info(`\u6F14\u7EC3\u5B8C\u6210\uFF1A\u5C06\u6821\u9A8C ${artifacts.length} \u4E2A Source Map \u6587\u4EF6`);
198
+ return;
199
+ }
200
+ return verify(config, artifacts);
201
+ }
202
+ main().catch((error) => {
203
+ console.error(error instanceof Error ? error.message : "CLI \u6267\u884C\u5931\u8D25");
204
+ process.exitCode = 1;
205
+ });
@@ -0,0 +1,13 @@
1
+ import { E as ErrorMonitor } from '../types-DxONWOH8.js';
2
+
3
+ interface ExpressRequestLike {
4
+ method?: unknown;
5
+ route?: {
6
+ path?: unknown;
7
+ };
8
+ }
9
+ type ExpressNextFunction = (error?: unknown) => void;
10
+ type ExpressErrorMiddleware = (error: unknown, request: ExpressRequestLike, response: unknown, next: ExpressNextFunction) => void;
11
+ declare function createExpressErrorMiddleware(monitor: ErrorMonitor): ExpressErrorMiddleware;
12
+
13
+ export { type ExpressErrorMiddleware, type ExpressNextFunction, type ExpressRequestLike, createExpressErrorMiddleware };
@@ -0,0 +1,22 @@
1
+ // src/express/index.ts
2
+ function requestTags(request) {
3
+ const tags = {};
4
+ if (typeof request.method === "string") tags.method = request.method.slice(0, 32);
5
+ if (typeof request.route?.path === "string") tags.route = request.route.path.slice(0, 512);
6
+ return tags;
7
+ }
8
+ function createExpressErrorMiddleware(monitor) {
9
+ return (error, request, _response, next) => {
10
+ try {
11
+ monitor.captureException(error, {
12
+ handled: false,
13
+ mechanism: "express.error",
14
+ tags: requestTags(request)
15
+ });
16
+ } finally {
17
+ next(error);
18
+ }
19
+ };
20
+ }
21
+
22
+ export { createExpressErrorMiddleware };
@@ -0,0 +1,19 @@
1
+ import { E as ErrorMonitor } from '../types-DxONWOH8.js';
2
+
3
+ interface FaultLensFastifyPluginOptions {
4
+ monitor: ErrorMonitor;
5
+ flushTimeoutMs?: number;
6
+ }
7
+ interface FastifyRequestLike {
8
+ method?: unknown;
9
+ routeOptions?: {
10
+ url?: unknown;
11
+ };
12
+ }
13
+ type FastifyHook = ((request: FastifyRequestLike, reply: unknown, error: unknown) => void | Promise<void>) | (() => void | Promise<void>);
14
+ interface FastifyInstanceLike {
15
+ addHook(name: string, hook: FastifyHook): unknown;
16
+ }
17
+ declare function faultLensFastifyPlugin(app: FastifyInstanceLike, options: FaultLensFastifyPluginOptions): Promise<void>;
18
+
19
+ export { type FaultLensFastifyPluginOptions, faultLensFastifyPlugin as default, faultLensFastifyPlugin };
@@ -0,0 +1,28 @@
1
+ // src/fastify/index.ts
2
+ function requestTags(request) {
3
+ const tags = {};
4
+ if (typeof request.method === "string") tags.method = request.method.slice(0, 32);
5
+ if (typeof request.routeOptions?.url === "string") {
6
+ tags.route = request.routeOptions.url.slice(0, 512);
7
+ }
8
+ return tags;
9
+ }
10
+ async function faultLensFastifyPlugin(app, options) {
11
+ const flushTimeoutMs = Math.min(3e4, Math.max(0, options.flushTimeoutMs ?? 2e3));
12
+ app.addHook("onError", async (request, _reply, error) => {
13
+ options.monitor.captureException(error, {
14
+ handled: false,
15
+ mechanism: "fastify.error",
16
+ tags: requestTags(request)
17
+ });
18
+ });
19
+ app.addHook("onClose", async () => {
20
+ try {
21
+ await options.monitor.flush(flushTimeoutMs);
22
+ } catch {
23
+ }
24
+ });
25
+ }
26
+ var fastify_default = faultLensFastifyPlugin;
27
+
28
+ export { fastify_default as default, faultLensFastifyPlugin };
@@ -0,0 +1,6 @@
1
+ import { M as MonitorOptions, E as ErrorMonitor } from './types-DxONWOH8.js';
2
+ export { B as Breadcrumb, C as CaptureOptions, a as ContextMode, I as IngestConfig, b as MonitorStatus } from './types-DxONWOH8.js';
3
+
4
+ declare function createErrorMonitor(options: MonitorOptions): ErrorMonitor;
5
+
6
+ export { ErrorMonitor, MonitorOptions, createErrorMonitor };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createErrorMonitor } from './chunk-K63I3OJT.js';
2
+ import './chunk-LRHXSFK2.js';
@@ -0,0 +1,34 @@
1
+ import { M as MonitorOptions, c as EventTransport, d as EventEnvelope, T as TransportOutcome, R as RuntimeDependencies, E as ErrorMonitor } from '../types-DxONWOH8.js';
2
+
3
+ interface NodeRelayIngestConfig {
4
+ mode: "relay";
5
+ endpoint: string;
6
+ keyId: string;
7
+ keyVersion: string;
8
+ secret: string;
9
+ }
10
+ interface NodeMonitorOptions extends Omit<MonitorOptions, "ingest"> {
11
+ ingest: NodeRelayIngestConfig;
12
+ captureGlobalErrors?: boolean;
13
+ }
14
+ interface NodeRelayTransportOptions {
15
+ fetcher?: typeof fetch;
16
+ maxAttempts?: number;
17
+ retryBaseMs?: number;
18
+ requestTimeoutMs?: number;
19
+ }
20
+ declare class NodeRelayTransport implements EventTransport {
21
+ private readonly ingest;
22
+ private readonly fetcher;
23
+ private readonly maxAttempts;
24
+ private readonly retryBaseMs;
25
+ private readonly requestTimeoutMs;
26
+ private readonly endpoint;
27
+ constructor(ingest: NodeRelayIngestConfig, options?: NodeRelayTransportOptions);
28
+ send(envelope: EventEnvelope): Promise<TransportOutcome>;
29
+ }
30
+ declare function createNodeErrorMonitor(options: NodeMonitorOptions, overrides?: {
31
+ transport?: EventTransport;
32
+ } & Partial<RuntimeDependencies>): ErrorMonitor;
33
+
34
+ export { type NodeMonitorOptions, type NodeRelayIngestConfig, NodeRelayTransport, type NodeRelayTransportOptions, createNodeErrorMonitor };
@@ -0,0 +1,117 @@
1
+ import { createMonitorRuntime } from '../chunk-LRHXSFK2.js';
2
+ import { createSignedRelayHeaders } from '../chunk-RXQF7F2E.js';
3
+
4
+ // src/node/index.ts
5
+ function retryAfter(response) {
6
+ const value = response.headers.get("retry-after");
7
+ return value && /^\d+$/.test(value) ? Number(value) * 1e3 : void 0;
8
+ }
9
+ function delay(timeoutMs) {
10
+ return new Promise((resolve) => setTimeout(resolve, timeoutMs));
11
+ }
12
+ var NodeRelayTransport = class {
13
+ constructor(ingest, options = {}) {
14
+ this.ingest = ingest;
15
+ const endpoint = new URL(ingest.endpoint);
16
+ const local = endpoint.hostname === "localhost" || endpoint.hostname === "127.0.0.1";
17
+ if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && local)) {
18
+ throw new Error("Node Relay Endpoint \u5FC5\u987B\u4F7F\u7528 HTTPS");
19
+ }
20
+ if (endpoint.pathname !== "/v1/relay/envelope" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
21
+ throw new Error("Node Relay Endpoint \u683C\u5F0F\u65E0\u6548");
22
+ }
23
+ if (!ingest.keyId || !ingest.keyVersion || !ingest.secret) {
24
+ throw new Error("Node Relay \u51ED\u636E\u4E0D\u5B8C\u6574");
25
+ }
26
+ this.endpoint = endpoint.toString();
27
+ this.fetcher = options.fetcher ?? fetch;
28
+ this.maxAttempts = Math.min(8, Math.max(1, options.maxAttempts ?? 3));
29
+ this.retryBaseMs = Math.min(3e4, Math.max(1, options.retryBaseMs ?? 250));
30
+ this.requestTimeoutMs = Math.min(12e4, Math.max(100, options.requestTimeoutMs ?? 5e3));
31
+ }
32
+ fetcher;
33
+ maxAttempts;
34
+ retryBaseMs;
35
+ requestTimeoutMs;
36
+ endpoint;
37
+ async send(envelope) {
38
+ const body = JSON.stringify(envelope);
39
+ const eventId = envelope.events[0]?.eventId;
40
+ if (!eventId) return { kind: "permanent_error", code: "empty_envelope" };
41
+ for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
44
+ try {
45
+ const response = await this.fetcher(this.endpoint, {
46
+ method: "POST",
47
+ headers: createSignedRelayHeaders({
48
+ upstream: this.endpoint,
49
+ relayKeyId: this.ingest.keyId,
50
+ relayKeyVersion: this.ingest.keyVersion,
51
+ relaySecret: this.ingest.secret
52
+ }, body, eventId),
53
+ body,
54
+ cache: "no-store",
55
+ redirect: "error",
56
+ signal: controller.signal
57
+ });
58
+ if (response.status === 202) return { kind: "accepted" };
59
+ if (response.status !== 429 && response.status < 500) {
60
+ let code = `http_${response.status}`;
61
+ try {
62
+ const value = await response.json();
63
+ if (typeof value.code === "string") code = value.code;
64
+ } catch {
65
+ }
66
+ return { kind: "permanent_error", code };
67
+ }
68
+ const waitMs = retryAfter(response) ?? Math.min(3e4, this.retryBaseMs * 2 ** attempt);
69
+ if (attempt + 1 >= this.maxAttempts) return { kind: "retryable", retryAfterMs: waitMs };
70
+ await delay(waitMs);
71
+ } catch {
72
+ if (attempt + 1 >= this.maxAttempts) return { kind: "network_error" };
73
+ await delay(Math.min(3e4, this.retryBaseMs * 2 ** attempt));
74
+ } finally {
75
+ clearTimeout(timer);
76
+ }
77
+ }
78
+ return { kind: "network_error" };
79
+ }
80
+ };
81
+ function createNodeErrorMonitor(options, overrides = {}) {
82
+ const { captureGlobalErrors = true, ingest, ...monitorOptions } = options;
83
+ const monitor = createMonitorRuntime(
84
+ {
85
+ ...monitorOptions,
86
+ ingest: { mode: "relay", endpoint: ingest.endpoint }
87
+ },
88
+ {
89
+ ...overrides,
90
+ transport: overrides.transport ?? new NodeRelayTransport(ingest)
91
+ }
92
+ );
93
+ if (!captureGlobalErrors) return monitor;
94
+ const onUncaughtException = (error) => {
95
+ monitor.captureException(error, {
96
+ handled: false,
97
+ mechanism: "node.uncaught_exception"
98
+ });
99
+ };
100
+ const onUnhandledRejection = (reason) => {
101
+ monitor.captureException(reason, {
102
+ handled: false,
103
+ mechanism: "node.unhandled_rejection"
104
+ });
105
+ };
106
+ process.on("uncaughtExceptionMonitor", onUncaughtException);
107
+ process.on("unhandledRejection", onUnhandledRejection);
108
+ const close = monitor.close;
109
+ monitor.close = async () => {
110
+ process.off("uncaughtExceptionMonitor", onUncaughtException);
111
+ process.off("unhandledRejection", onUnhandledRejection);
112
+ return close();
113
+ };
114
+ return monitor;
115
+ }
116
+
117
+ export { NodeRelayTransport, createNodeErrorMonitor };
@@ -0,0 +1,23 @@
1
+ import { NuxtModule } from '@nuxt/schema';
2
+
3
+ interface RelayRuntimeConfig {
4
+ upstream: string;
5
+ relayKeyId: string;
6
+ relayKeyVersion: string;
7
+ relaySecret: string;
8
+ }
9
+ declare function createSignedRelayHeaders(config: RelayRuntimeConfig, body: string, eventId: string, timestamp?: number): Record<string, string>;
10
+
11
+ interface FaultLensNuxtModuleOptions {
12
+ environment: string;
13
+ release: string;
14
+ dist?: string;
15
+ relayEndpoint?: string;
16
+ upstream?: string;
17
+ relayKeyId?: string;
18
+ relayKeyVersion?: string;
19
+ relaySecret?: string;
20
+ }
21
+ declare const faultLensNuxtModule: NuxtModule<FaultLensNuxtModuleOptions>;
22
+
23
+ export { type FaultLensNuxtModuleOptions, createSignedRelayHeaders, faultLensNuxtModule as default };
@@ -0,0 +1,52 @@
1
+ export { createSignedRelayHeaders } from '../chunk-RXQF7F2E.js';
2
+ import { defineNuxtModule, createResolver, addPlugin, addServerHandler, addServerPlugin } from '@nuxt/kit';
3
+
4
+ var faultLensNuxtModule = defineNuxtModule({
5
+ meta: {
6
+ name: "@devkitio/faultlens",
7
+ configKey: "faultLens",
8
+ compatibility: { nuxt: ">=4.0.0" }
9
+ },
10
+ defaults: {
11
+ environment: "development",
12
+ release: "nuxt@0.0.0+development",
13
+ dist: "default",
14
+ relayEndpoint: "/__faultlens/envelope",
15
+ relayKeyVersion: "v1"
16
+ },
17
+ setup(options, nuxt) {
18
+ const resolver = createResolver(import.meta.url);
19
+ nuxt.options.runtimeConfig.public.faultLens = {
20
+ environment: options.environment,
21
+ release: options.release,
22
+ dist: options.dist,
23
+ relayEndpoint: options.relayEndpoint
24
+ };
25
+ nuxt.options.runtimeConfig.faultLens = {
26
+ environment: options.environment,
27
+ release: options.release,
28
+ dist: options.dist,
29
+ upstream: options.upstream,
30
+ relayKeyId: options.relayKeyId,
31
+ relayKeyVersion: options.relayKeyVersion,
32
+ relaySecret: options.relaySecret
33
+ };
34
+ addPlugin({
35
+ src: resolver.resolve("./runtime/plugin"),
36
+ mode: "client",
37
+ order: -100
38
+ });
39
+ addServerHandler({
40
+ route: options.relayEndpoint ?? "/__faultlens/envelope",
41
+ handler: resolver.resolve("./runtime/server-handler")
42
+ });
43
+ addServerHandler({
44
+ route: `${options.relayEndpoint ?? "/__faultlens/envelope"}/config`,
45
+ handler: resolver.resolve("./runtime/config-handler")
46
+ });
47
+ addServerPlugin(resolver.resolve("./runtime/nitro-plugin"));
48
+ }
49
+ });
50
+ var nuxt_default = faultLensNuxtModule;
51
+
52
+ export { nuxt_default as default };
@@ -0,0 +1,5 @@
1
+ import * as h3 from 'h3';
2
+
3
+ declare const _default: h3.EventHandler<h3.EventHandlerRequest, Promise<unknown>>;
4
+
5
+ export { _default as default };
@@ -0,0 +1,32 @@
1
+ import { defineEventHandler, setResponseStatus, setResponseHeader } from 'h3';
2
+
3
+ // src/nuxt/runtime/config-handler.ts
4
+ var config_handler_default = defineEventHandler(async (event) => {
5
+ const runtime = useRuntimeConfig(event).faultLens;
6
+ if (!runtime.upstream || !runtime.relayKeyId || !runtime.relayKeyVersion) {
7
+ setResponseStatus(event, 503);
8
+ return { code: "relay_not_configured" };
9
+ }
10
+ try {
11
+ const upstream = new URL(runtime.upstream);
12
+ upstream.pathname = "/v1/relay/config";
13
+ upstream.search = "";
14
+ const response = await fetch(upstream, {
15
+ method: "GET",
16
+ headers: {
17
+ "x-faultlens-key-id": runtime.relayKeyId,
18
+ "x-faultlens-key-version": runtime.relayKeyVersion
19
+ },
20
+ redirect: "error",
21
+ cache: "no-store"
22
+ });
23
+ setResponseStatus(event, response.status);
24
+ setResponseHeader(event, "cache-control", "no-store");
25
+ return await response.json();
26
+ } catch {
27
+ setResponseStatus(event, 503);
28
+ return { code: "relay_unavailable" };
29
+ }
30
+ });
31
+
32
+ export { config_handler_default as default };
@@ -0,0 +1,3 @@
1
+ declare const _default: unknown;
2
+
3
+ export { _default as default };