@greenhat-services/magpie-node 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Magpie contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # @greenhat-services/magpie-node
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Magpie error tracking and performance monitoring for Node.js servers, including Nuxt's Nitro.
4
+
5
+ ```sh
6
+ npm install @greenhat-services/magpie-node
7
+ ```
8
+
9
+ Per-request context isolated with AsyncLocalStorage, request tracing that continues the browser's trace, and the server's own `fetch` calls as spans.
10
+
11
+ ```ts
12
+ import * as Magpie from '@greenhat-services/magpie-node';
13
+
14
+ const client = Magpie.init({ dsn: 'https://<key>@<host>/<project>', tracesSampleRate: 0.2 });
15
+ ```
16
+
17
+ Using Nuxt? Install `@greenhat-services/magpie-nuxt` instead, which sets this up for you.
18
+
19
+ Full documentation, options and what is (and is not) sent: https://github.com/Greenhat-Services/magpie-js#readme
20
+
21
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,262 @@
1
+ 'use strict';
2
+
3
+ var magpieCore = require('@greenhat-services/magpie-core');
4
+ var async_hooks = require('async_hooks');
5
+ var os = require('os');
6
+ var zlib = require('zlib');
7
+
8
+ // src/index.ts
9
+ var storage = new async_hooks.AsyncLocalStorage();
10
+ var currentStore = () => storage.getStore();
11
+ function isolatePerRequest(client) {
12
+ let processScope = client.scope;
13
+ let processTransaction = client.activeTransaction;
14
+ Object.defineProperty(client, "scope", {
15
+ configurable: true,
16
+ get: () => storage.getStore()?.scope ?? processScope,
17
+ // withScope() swaps in a copy while its callback runs and puts the original back afterwards. Inside a request
18
+ // that swap belongs to the request, so one request's temporary tags can never reach another.
19
+ set: (scope) => {
20
+ const store = storage.getStore();
21
+ if (store) store.scope = scope;
22
+ else processScope = scope;
23
+ }
24
+ });
25
+ Object.defineProperty(client, "activeTransaction", {
26
+ configurable: true,
27
+ get: () => {
28
+ const store = storage.getStore();
29
+ return store ? store.transaction : processTransaction;
30
+ },
31
+ set: (tx) => {
32
+ const store = storage.getStore();
33
+ if (store) store.transaction = tx;
34
+ else processTransaction = tx;
35
+ }
36
+ });
37
+ }
38
+ function runWithRequest(client, request, fn) {
39
+ return storage.run({ scope: client.scope.clone(), transaction: void 0, request }, fn);
40
+ }
41
+
42
+ // src/handlers.ts
43
+ function processHandlers() {
44
+ return {
45
+ name: "ProcessHandlers",
46
+ setup(client) {
47
+ const onUncaught = (error) => {
48
+ client.captureException(error, { mechanism: { type: "onuncaughtexception", handled: false } });
49
+ void client.flush(2e3).finally(() => {
50
+ if (process.listenerCount("uncaughtException") <= 1) {
51
+ console.error(error);
52
+ process.exit(1);
53
+ }
54
+ });
55
+ };
56
+ const onRejection = (reason) => {
57
+ client.captureException(reason, { mechanism: { type: "onunhandledrejection", handled: false } });
58
+ if (process.listenerCount("unhandledRejection") <= 1) {
59
+ setImmediate(() => {
60
+ throw reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${String(reason)}`);
61
+ });
62
+ }
63
+ };
64
+ process.on("uncaughtException", onUncaught);
65
+ process.on("unhandledRejection", onRejection);
66
+ return () => {
67
+ process.off("uncaughtException", onUncaught);
68
+ process.off("unhandledRejection", onRejection);
69
+ };
70
+ }
71
+ };
72
+ }
73
+ function nodeHttp() {
74
+ return {
75
+ name: "NodeHttp",
76
+ setup(client) {
77
+ if (typeof globalThis.fetch !== "function") return;
78
+ const original = globalThis.fetch;
79
+ const skip = (url) => !!client.dsn && url.startsWith(client.dsn.origin);
80
+ globalThis.fetch = function patchedFetch(input, init2) {
81
+ const isRequest = typeof Request !== "undefined" && input instanceof Request;
82
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : isRequest ? input.url : String(input);
83
+ const method = (init2?.method ?? (isRequest ? input.method : "GET")).toUpperCase();
84
+ if (skip(url)) return original.call(this, input, init2);
85
+ const tx = client.activeTransaction;
86
+ const started = performance.now();
87
+ let span;
88
+ if (tx?.sampled) span = tx.startChild({ op: "http.client", description: `${method} ${magpieCore.scrubUrl(url).split(/[?#]/)[0]}`, data: { method } });
89
+ const propagate = (client.options.tracesSampleRate ?? 0) > 0 && !!tx && (client.options.tracePropagationTargets === void 0 || client.shouldPropagate(url));
90
+ let args = [input, init2];
91
+ if (propagate && tx) {
92
+ const headers = new Headers(init2?.headers ?? (isRequest ? input.headers : void 0));
93
+ if (!headers.has(magpieCore.TRACE_HEADER)) headers.set(magpieCore.TRACE_HEADER, magpieCore.formatTraceHeader(tx.traceId, span?.spanId ?? tx.spanId, tx.sampled));
94
+ args = [input, { ...init2, headers }];
95
+ }
96
+ const finish = (status) => {
97
+ if (span) {
98
+ if (status) span.setData("status_code", status);
99
+ span.setStatus(status === 0 ? "unknown_error" : status >= 500 ? "internal_error" : "ok");
100
+ span.end();
101
+ }
102
+ client.addBreadcrumb({
103
+ category: "http",
104
+ type: "http",
105
+ level: status >= 500 || status === 0 ? "error" : status >= 400 ? "warning" : "info",
106
+ message: `${method} ${magpieCore.scrubUrl(url)}`,
107
+ data: { method, status_code: status || "failed", duration_ms: Math.round(performance.now() - started) }
108
+ });
109
+ };
110
+ return original.apply(this, args).then(
111
+ (response) => {
112
+ finish(response.status);
113
+ return response;
114
+ },
115
+ (error) => {
116
+ finish(0);
117
+ throw error;
118
+ }
119
+ );
120
+ };
121
+ return () => {
122
+ globalThis.fetch = original;
123
+ };
124
+ }
125
+ };
126
+ }
127
+ var COMPRESS_FROM = 4096;
128
+ function nodeTransport(dsn, fetchImpl = (...args) => globalThis.fetch(...args), timeoutMs = 3e3) {
129
+ return {
130
+ async send(payload) {
131
+ try {
132
+ const path = payload.type === "transaction" ? "transactions" : "events";
133
+ const json = JSON.stringify(payload);
134
+ const compressed = json.length >= COMPRESS_FROM;
135
+ const response = await fetchImpl(magpieCore.endpoint(dsn, path), {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ "X-Magpie-Auth": `Magpie magpie_version=1, magpie_key=${dsn.publicKey}, magpie_client=magpie-node`,
140
+ ...compressed ? { "Content-Encoding": "gzip" } : {}
141
+ },
142
+ body: compressed ? new Uint8Array(zlib.gzipSync(json)) : json,
143
+ signal: AbortSignal.timeout(timeoutMs)
144
+ });
145
+ const retry = Number(response.headers.get("retry-after"));
146
+ return { ok: response.ok, status: response.status, retryAfter: Number.isFinite(retry) && retry > 0 ? Math.min(retry, 3600) : void 0 };
147
+ } catch {
148
+ return { ok: false };
149
+ }
150
+ }
151
+ };
152
+ }
153
+
154
+ // src/runtime.ts
155
+ var SDK_NAME = "magpie.javascript.node";
156
+ var OS_NAMES = { win32: "Windows", darwin: "macOS", linux: "Linux", freebsd: "FreeBSD" };
157
+ function nodeRuntime(options = {}) {
158
+ return {
159
+ sdk: { name: options.sdkName ?? SDK_NAME, version: magpieCore.SDK_VERSION },
160
+ platform: "node",
161
+ createTransport: (dsn) => nodeTransport(dsn, options.fetch),
162
+ contexts: () => ({
163
+ runtime: { name: "node", version: process.version.replace(/^v/, "") },
164
+ os: { name: OS_NAMES[os.platform()] ?? os.platform(), version: os.release() }
165
+ }),
166
+ // The request being served, when there is one: set by `trackRequest`, read here for every event.
167
+ request: () => currentStore()?.request
168
+ };
169
+ }
170
+ var defaultServerName = () => {
171
+ try {
172
+ return os.hostname() || void 0;
173
+ } catch {
174
+ return void 0;
175
+ }
176
+ };
177
+ var SAFE_HEADERS = ["user-agent", "accept", "accept-language", "content-type", "host", "referer", "x-forwarded-proto"];
178
+ function trackRequest(client, info, fn) {
179
+ const { path, query } = splitUrl(info.url);
180
+ const request = {
181
+ url: info.headers?.host ? `${firstHeader(info.headers["x-forwarded-proto"]) ?? "http"}://${firstHeader(info.headers.host)}${path}` : path,
182
+ method: info.method.toUpperCase(),
183
+ ...query ? { query_string: magpieCore.scrubQuery(query) } : {},
184
+ headers: safeHeaders(info.headers)
185
+ };
186
+ return runWithRequest(client, request, () => {
187
+ const parent = magpieCore.parseTraceHeader(firstHeader(info.headers?.[magpieCore.TRACE_HEADER]));
188
+ const method = info.method.toUpperCase();
189
+ const name = (route) => `${method} ${route ?? info.route ?? path}`;
190
+ const transaction = client.startTransaction({
191
+ name: name(),
192
+ op: "http.server",
193
+ ...parent ? { traceId: parent.traceId, parentSpanId: parent.spanId, sampled: parent.sampled } : {}
194
+ });
195
+ client.activeTransaction = transaction;
196
+ client.scope.setTransactionName(transaction.name);
197
+ const finish = (statusCode, route) => {
198
+ transaction.setName(name(route));
199
+ transaction.setData("http.status_code", statusCode);
200
+ if (statusCode >= 500) transaction.setStatus("internal_error");
201
+ transaction.end();
202
+ };
203
+ return fn({ transaction, finish });
204
+ });
205
+ }
206
+ function splitUrl(url) {
207
+ let target = url;
208
+ try {
209
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
210
+ const u = new URL(url);
211
+ target = `${u.pathname}${u.search}`;
212
+ }
213
+ } catch {
214
+ }
215
+ const q = target.indexOf("?");
216
+ return q < 0 ? { path: target, query: "" } : { path: target.slice(0, q), query: target.slice(q + 1) };
217
+ }
218
+ function firstHeader(value) {
219
+ return Array.isArray(value) ? value[0] : value;
220
+ }
221
+ function safeHeaders(headers) {
222
+ const out = {};
223
+ for (const name of SAFE_HEADERS) {
224
+ const value = firstHeader(headers?.[name]);
225
+ if (value) out[name] = value;
226
+ }
227
+ return out;
228
+ }
229
+
230
+ // src/index.ts
231
+ function init(options = {}) {
232
+ const previous = magpieCore.getClient();
233
+ if (previous) void previous.close(0);
234
+ const { captureUnhandled = true, http = true, sdkName, integrations = [], ...rest } = options;
235
+ const client = new magpieCore.Client(
236
+ { serverName: defaultServerName(), ...rest, release: rest.release ?? process.env.MAGPIE_RELEASE },
237
+ nodeRuntime({ sdkName })
238
+ );
239
+ isolatePerRequest(client);
240
+ magpieCore.setClient(client);
241
+ const defaults = [...captureUnhandled ? [processHandlers()] : [], ...http ? [nodeHttp()] : []];
242
+ client.setupIntegrations([...defaults, ...integrations]);
243
+ return client;
244
+ }
245
+
246
+ exports.currentStore = currentStore;
247
+ exports.init = init;
248
+ exports.isolatePerRequest = isolatePerRequest;
249
+ exports.nodeHttp = nodeHttp;
250
+ exports.nodeTransport = nodeTransport;
251
+ exports.processHandlers = processHandlers;
252
+ exports.runWithRequest = runWithRequest;
253
+ exports.storage = storage;
254
+ exports.trackRequest = trackRequest;
255
+ Object.keys(magpieCore).forEach(function (k) {
256
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
257
+ enumerable: true,
258
+ get: function () { return magpieCore[k]; }
259
+ });
260
+ });
261
+ //# sourceMappingURL=index.cjs.map
262
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts","../src/handlers.ts","../src/http.ts","../src/transport.ts","../src/runtime.ts","../src/request.ts","../src/index.ts"],"names":["AsyncLocalStorage","init","scrubUrl","TRACE_HEADER","formatTraceHeader","endpoint","gzipSync","SDK_VERSION","platform","release","hostname","scrubQuery","parseTraceHeader","getClient","Client","setClient"],"mappings":"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","file":"index.cjs","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Client, MagpieEvent, Scope, Transaction } from '@greenhat-services/magpie-core';\n\n/**\n * What belongs to one request on a server that handles many at once: its own scope (tags, user, breadcrumbs), the\n * transaction being timed, and the request being served. Carried through every `await` by AsyncLocalStorage, so two\n * overlapping requests never see each other's data.\n */\nexport interface RequestStore {\n scope: Scope;\n transaction: Transaction | undefined;\n request: MagpieEvent['request'] | undefined;\n}\n\nexport const storage = new AsyncLocalStorage<RequestStore>();\n\nexport const currentStore = (): RequestStore | undefined => storage.getStore();\n\n/**\n * Makes `client.scope` and `client.activeTransaction` follow the request that is running, and fall back to the\n * process-wide ones outside any request. Applied to the instance, so core stays free of Node.\n */\nexport function isolatePerRequest(client: Client): void {\n let processScope = client.scope;\n let processTransaction = client.activeTransaction;\n\n Object.defineProperty(client, 'scope', {\n configurable: true,\n get: () => storage.getStore()?.scope ?? processScope,\n // withScope() swaps in a copy while its callback runs and puts the original back afterwards. Inside a request\n // that swap belongs to the request, so one request's temporary tags can never reach another.\n set: (scope: Scope) => {\n const store = storage.getStore();\n if (store) store.scope = scope;\n else processScope = scope;\n },\n });\n\n Object.defineProperty(client, 'activeTransaction', {\n configurable: true,\n get: () => {\n const store = storage.getStore();\n\n return store ? store.transaction : processTransaction;\n },\n set: (tx: Transaction | undefined) => {\n const store = storage.getStore();\n if (store) store.transaction = tx;\n else processTransaction = tx;\n },\n });\n}\n\n/** Runs `fn` as one request: a copy of the process scope, its own transaction slot, and the request being served. */\nexport function runWithRequest<T>(client: Client, request: MagpieEvent['request'] | undefined, fn: () => T): T {\n return storage.run({ scope: client.scope.clone(), transaction: undefined, request }, fn);\n}\n","import type { Client, Integration } from '@greenhat-services/magpie-core';\n\n/**\n * Reports what the process did not catch. Adding a listener for either event removes Node's own reaction to it (print\n * and exit), so after reporting, that reaction is restored unless something else in the process is handling it.\n */\nexport function processHandlers(): Integration {\n return {\n name: 'ProcessHandlers',\n setup(client: Client) {\n const onUncaught = (error: Error) => {\n client.captureException(error, { mechanism: { type: 'onuncaughtexception', handled: false } });\n\n void client.flush(2000).finally(() => {\n if (process.listenerCount('uncaughtException') <= 1) {\n console.error(error);\n process.exit(1);\n }\n });\n };\n\n const onRejection = (reason: unknown) => {\n client.captureException(reason, { mechanism: { type: 'onunhandledrejection', handled: false } });\n\n // Node's default since v15 is to raise an unhandled rejection as an uncaught exception. Keep it, so a\n // server that crashed before Magpie still crashes (and the crash is already reported).\n if (process.listenerCount('unhandledRejection') <= 1) {\n setImmediate(() => {\n throw reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${String(reason)}`);\n });\n }\n };\n\n process.on('uncaughtException', onUncaught);\n process.on('unhandledRejection', onRejection);\n\n return () => {\n process.off('uncaughtException', onUncaught);\n process.off('unhandledRejection', onRejection);\n };\n },\n };\n}\n","import { type Client, formatTraceHeader, type Integration, scrubUrl, type Span, TRACE_HEADER } from '@greenhat-services/magpie-core';\n\n/**\n * Times the server's own outgoing `fetch` calls (an API your pages render from). Each becomes a breadcrumb on the\n * current request and, when that request is being traced, a span of it, and carries the `magpie-trace` header so\n * the service on the other end continues the trace: browser, Nuxt server and Laravel API as one.\n *\n * The header goes to every host unless `tracePropagationTargets` lists the ones that should get it.\n */\nexport function nodeHttp(): Integration {\n return {\n name: 'NodeHttp',\n setup(client: Client) {\n if (typeof globalThis.fetch !== 'function') return;\n\n const original = globalThis.fetch;\n const skip = (url: string) => !!client.dsn && url.startsWith(client.dsn.origin);\n\n globalThis.fetch = function patchedFetch(this: unknown, input: Parameters<typeof fetch>[0], init?: RequestInit) {\n const isRequest = typeof Request !== 'undefined' && input instanceof Request;\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : isRequest ? (input as Request).url : String(input);\n const method = (init?.method ?? (isRequest ? (input as Request).method : 'GET')).toUpperCase();\n\n if (skip(url)) return original.call(this, input, init);\n\n const tx = client.activeTransaction;\n const started = performance.now();\n let span: Span | undefined;\n if (tx?.sampled) span = tx.startChild({ op: 'http.client', description: `${method} ${scrubUrl(url).split(/[?#]/)[0]}`, data: { method } });\n\n const propagate = (client.options.tracesSampleRate ?? 0) > 0 && !!tx && (client.options.tracePropagationTargets === undefined || client.shouldPropagate(url));\n let args: [Parameters<typeof fetch>[0], RequestInit | undefined] = [input, init];\n if (propagate && tx) {\n const headers = new Headers(init?.headers ?? (isRequest ? (input as Request).headers : undefined));\n if (!headers.has(TRACE_HEADER)) headers.set(TRACE_HEADER, formatTraceHeader(tx.traceId, span?.spanId ?? tx.spanId, tx.sampled));\n args = [input, { ...init, headers }];\n }\n\n const finish = (status: number) => {\n if (span) {\n if (status) span.setData('status_code', status);\n span.setStatus(status === 0 ? 'unknown_error' : status >= 500 ? 'internal_error' : 'ok');\n span.end();\n }\n client.addBreadcrumb({\n category: 'http',\n type: 'http',\n level: status >= 500 || status === 0 ? 'error' : status >= 400 ? 'warning' : 'info',\n message: `${method} ${scrubUrl(url)}`,\n data: { method, status_code: status || 'failed', duration_ms: Math.round(performance.now() - started) },\n });\n };\n\n return original.apply(this, args).then(\n (response) => {\n finish(response.status);\n\n return response;\n },\n (error) => {\n finish(0);\n throw error;\n },\n );\n } as typeof fetch;\n\n return () => {\n globalThis.fetch = original;\n };\n },\n };\n}\n","import { gzipSync } from 'node:zlib';\nimport { type Dsn, endpoint, type Payload, type Transport, type TransportResult } from '@greenhat-services/magpie-core';\n\n/** Bodies above this are gzip-compressed, which Magpie accepts. */\nconst COMPRESS_FROM = 4096;\n\n/**\n * Delivers payloads from a server. There is no CORS to avoid here, so the key goes in the `X-Magpie-Auth` header,\n * the same as the Laravel SDK, and the body is JSON, compressed when it is large.\n */\nexport function nodeTransport(dsn: Dsn, fetchImpl: typeof fetch = (...args) => globalThis.fetch(...args), timeoutMs = 3000): Transport {\n return {\n async send(payload: Payload): Promise<TransportResult> {\n try {\n const path = (payload as { type?: string }).type === 'transaction' ? 'transactions' : 'events';\n const json = JSON.stringify(payload);\n const compressed = json.length >= COMPRESS_FROM;\n\n const response = await fetchImpl(endpoint(dsn, path), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Magpie-Auth': `Magpie magpie_version=1, magpie_key=${dsn.publicKey}, magpie_client=magpie-node`,\n ...(compressed ? { 'Content-Encoding': 'gzip' } : {}),\n },\n body: compressed ? new Uint8Array(gzipSync(json)) : json,\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n const retry = Number(response.headers.get('retry-after'));\n\n return { ok: response.ok, status: response.status, retryAfter: Number.isFinite(retry) && retry > 0 ? Math.min(retry, 3600) : undefined };\n } catch {\n // Unreachable, or too slow: monitoring must never hold a request up, so the payload is dropped.\n return { ok: false };\n }\n },\n };\n}\n","import { hostname, platform, release } from 'node:os';\nimport { type Runtime, SDK_VERSION } from '@greenhat-services/magpie-core';\nimport { currentStore } from './context';\nimport { nodeTransport } from './transport';\n\nexport const SDK_NAME = 'magpie.javascript.node';\n\nconst OS_NAMES: Record<string, string> = { win32: 'Windows', darwin: 'macOS', linux: 'Linux', freebsd: 'FreeBSD' };\n\nexport function nodeRuntime(options: { sdkName?: string; fetch?: typeof fetch } = {}): Runtime {\n return {\n sdk: { name: options.sdkName ?? SDK_NAME, version: SDK_VERSION },\n platform: 'node',\n createTransport: (dsn) => nodeTransport(dsn, options.fetch),\n contexts: () => ({\n runtime: { name: 'node', version: process.version.replace(/^v/, '') },\n os: { name: OS_NAMES[platform()] ?? platform(), version: release() },\n }),\n // The request being served, when there is one: set by `trackRequest`, read here for every event.\n request: () => currentStore()?.request,\n };\n}\n\nexport const defaultServerName = (): string | undefined => {\n try {\n return hostname() || undefined;\n } catch {\n return undefined;\n }\n};\n","import { type Client, parseTraceHeader, scrubQuery, TRACE_HEADER, type Transaction } from '@greenhat-services/magpie-core';\nimport { runWithRequest } from './context';\n\nexport interface RequestInfo {\n method: string;\n /** The path (and query string), or a full URL. */\n url: string;\n /** The request's headers, lower-case names. Only a short allow-list of them is ever attached to an event. */\n headers?: Record<string, string | string[] | undefined>;\n /** The matched route pattern (`/users/:id`) when the framework already knows it. */\n route?: string;\n}\n\n/** Headers worth having on an event. Everything else, cookies and authorization above all, is dropped, not filtered. */\nconst SAFE_HEADERS = ['user-agent', 'accept', 'accept-language', 'content-type', 'host', 'referer', 'x-forwarded-proto'];\n\nexport interface RequestHandle {\n transaction: Transaction;\n /** Ends the request's transaction. Pass the route pattern when it is only known now, so `/users/7` becomes `/users/:id`. */\n finish(statusCode: number, route?: string): void;\n}\n\n/**\n * Runs `fn` as one request: its own scope and transaction, the request attached to every event raised inside it, and a\n * trace that continues the caller's `magpie-trace` header (so a page load in the browser and this server render are one\n * trace). Call `finish` when the response is done.\n */\nexport function trackRequest<T>(client: Client, info: RequestInfo, fn: (handle: RequestHandle) => T): T {\n const { path, query } = splitUrl(info.url);\n const request = {\n url: info.headers?.host ? `${firstHeader(info.headers['x-forwarded-proto']) ?? 'http'}://${firstHeader(info.headers.host)}${path}` : path,\n method: info.method.toUpperCase(),\n ...(query ? { query_string: scrubQuery(query) } : {}),\n headers: safeHeaders(info.headers),\n };\n\n return runWithRequest(client, request, () => {\n const parent = parseTraceHeader(firstHeader(info.headers?.[TRACE_HEADER]));\n const method = info.method.toUpperCase();\n const name = (route?: string) => `${method} ${route ?? info.route ?? path}`;\n\n const transaction = client.startTransaction({\n name: name(),\n op: 'http.server',\n ...(parent ? { traceId: parent.traceId, parentSpanId: parent.spanId, sampled: parent.sampled } : {}),\n });\n client.activeTransaction = transaction;\n client.scope.setTransactionName(transaction.name);\n\n const finish = (statusCode: number, route?: string) => {\n transaction.setName(name(route));\n transaction.setData('http.status_code', statusCode);\n // Magpie counts any status but \"ok\" as a failure, so only a server error is one. A 404 is a success.\n if (statusCode >= 500) transaction.setStatus('internal_error');\n transaction.end();\n };\n\n return fn({ transaction, finish });\n });\n}\n\nfunction splitUrl(url: string): { path: string; query: string } {\n let target = url;\n try {\n if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {\n const u = new URL(url);\n target = `${u.pathname}${u.search}`;\n }\n } catch {\n // Keep it as given.\n }\n const q = target.indexOf('?');\n\n return q < 0 ? { path: target, query: '' } : { path: target.slice(0, q), query: target.slice(q + 1) };\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction safeHeaders(headers: RequestInfo['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of SAFE_HEADERS) {\n const value = firstHeader(headers?.[name]);\n if (value) out[name] = value;\n }\n\n return out;\n}\n","import { Client, type Integration, type Options, setClient, getClient } from '@greenhat-services/magpie-core';\nimport { isolatePerRequest } from './context';\nimport { processHandlers } from './handlers';\nimport { nodeHttp } from './http';\nimport { defaultServerName, nodeRuntime } from './runtime';\n\nexport * from '@greenhat-services/magpie-core';\nexport { currentStore, isolatePerRequest, runWithRequest, storage } from './context';\nexport { processHandlers } from './handlers';\nexport { nodeHttp } from './http';\nexport { trackRequest, type RequestHandle, type RequestInfo } from './request';\nexport { nodeTransport } from './transport';\n\nexport interface NodeOptions extends Options {\n /** Report uncaught exceptions and unhandled rejections. Default true. */\n captureUnhandled?: boolean;\n /** Time and propagate the server's own `fetch` calls. Default true. */\n http?: boolean;\n sdkName?: string;\n}\n\n/**\n * Starts Magpie on a server. Per-request data (scope, transaction, the request) is isolated with AsyncLocalStorage, so\n * use `trackRequest` around each request the framework handles.\n */\nexport function init(options: NodeOptions = {}): Client {\n const previous = getClient();\n if (previous) void previous.close(0);\n\n const { captureUnhandled = true, http = true, sdkName, integrations = [], ...rest } = options;\n\n const client = new Client(\n { serverName: defaultServerName(), ...rest, release: rest.release ?? process.env.MAGPIE_RELEASE },\n nodeRuntime({ sdkName }),\n );\n\n isolatePerRequest(client);\n setClient(client);\n\n const defaults: Integration[] = [...(captureUnhandled ? [processHandlers()] : []), ...(http ? [nodeHttp()] : [])];\n client.setupIntegrations([...defaults, ...integrations]);\n\n return client;\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import { Scope, Transaction, MagpieEvent, Client, Integration, Dsn, Transport, Options } from '@greenhat-services/magpie-core';
2
+ export * from '@greenhat-services/magpie-core';
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
4
+
5
+ /**
6
+ * What belongs to one request on a server that handles many at once: its own scope (tags, user, breadcrumbs), the
7
+ * transaction being timed, and the request being served. Carried through every `await` by AsyncLocalStorage, so two
8
+ * overlapping requests never see each other's data.
9
+ */
10
+ interface RequestStore {
11
+ scope: Scope;
12
+ transaction: Transaction | undefined;
13
+ request: MagpieEvent['request'] | undefined;
14
+ }
15
+ declare const storage: AsyncLocalStorage<RequestStore>;
16
+ declare const currentStore: () => RequestStore | undefined;
17
+ /**
18
+ * Makes `client.scope` and `client.activeTransaction` follow the request that is running, and fall back to the
19
+ * process-wide ones outside any request. Applied to the instance, so core stays free of Node.
20
+ */
21
+ declare function isolatePerRequest(client: Client): void;
22
+ /** Runs `fn` as one request: a copy of the process scope, its own transaction slot, and the request being served. */
23
+ declare function runWithRequest<T>(client: Client, request: MagpieEvent['request'] | undefined, fn: () => T): T;
24
+
25
+ /**
26
+ * Reports what the process did not catch. Adding a listener for either event removes Node's own reaction to it (print
27
+ * and exit), so after reporting, that reaction is restored unless something else in the process is handling it.
28
+ */
29
+ declare function processHandlers(): Integration;
30
+
31
+ /**
32
+ * Times the server's own outgoing `fetch` calls (an API your pages render from). Each becomes a breadcrumb on the
33
+ * current request and, when that request is being traced, a span of it, and carries the `magpie-trace` header so
34
+ * the service on the other end continues the trace: browser, Nuxt server and Laravel API as one.
35
+ *
36
+ * The header goes to every host unless `tracePropagationTargets` lists the ones that should get it.
37
+ */
38
+ declare function nodeHttp(): Integration;
39
+
40
+ interface RequestInfo {
41
+ method: string;
42
+ /** The path (and query string), or a full URL. */
43
+ url: string;
44
+ /** The request's headers, lower-case names. Only a short allow-list of them is ever attached to an event. */
45
+ headers?: Record<string, string | string[] | undefined>;
46
+ /** The matched route pattern (`/users/:id`) when the framework already knows it. */
47
+ route?: string;
48
+ }
49
+ interface RequestHandle {
50
+ transaction: Transaction;
51
+ /** Ends the request's transaction. Pass the route pattern when it is only known now, so `/users/7` becomes `/users/:id`. */
52
+ finish(statusCode: number, route?: string): void;
53
+ }
54
+ /**
55
+ * Runs `fn` as one request: its own scope and transaction, the request attached to every event raised inside it, and a
56
+ * trace that continues the caller's `magpie-trace` header (so a page load in the browser and this server render are one
57
+ * trace). Call `finish` when the response is done.
58
+ */
59
+ declare function trackRequest<T>(client: Client, info: RequestInfo, fn: (handle: RequestHandle) => T): T;
60
+
61
+ /**
62
+ * Delivers payloads from a server. There is no CORS to avoid here, so the key goes in the `X-Magpie-Auth` header,
63
+ * the same as the Laravel SDK, and the body is JSON, compressed when it is large.
64
+ */
65
+ declare function nodeTransport(dsn: Dsn, fetchImpl?: typeof fetch, timeoutMs?: number): Transport;
66
+
67
+ interface NodeOptions extends Options {
68
+ /** Report uncaught exceptions and unhandled rejections. Default true. */
69
+ captureUnhandled?: boolean;
70
+ /** Time and propagate the server's own `fetch` calls. Default true. */
71
+ http?: boolean;
72
+ sdkName?: string;
73
+ }
74
+ /**
75
+ * Starts Magpie on a server. Per-request data (scope, transaction, the request) is isolated with AsyncLocalStorage, so
76
+ * use `trackRequest` around each request the framework handles.
77
+ */
78
+ declare function init(options?: NodeOptions): Client;
79
+
80
+ export { type NodeOptions, type RequestHandle, type RequestInfo, currentStore, init, isolatePerRequest, nodeHttp, nodeTransport, processHandlers, runWithRequest, storage, trackRequest };
@@ -0,0 +1,80 @@
1
+ import { Scope, Transaction, MagpieEvent, Client, Integration, Dsn, Transport, Options } from '@greenhat-services/magpie-core';
2
+ export * from '@greenhat-services/magpie-core';
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
4
+
5
+ /**
6
+ * What belongs to one request on a server that handles many at once: its own scope (tags, user, breadcrumbs), the
7
+ * transaction being timed, and the request being served. Carried through every `await` by AsyncLocalStorage, so two
8
+ * overlapping requests never see each other's data.
9
+ */
10
+ interface RequestStore {
11
+ scope: Scope;
12
+ transaction: Transaction | undefined;
13
+ request: MagpieEvent['request'] | undefined;
14
+ }
15
+ declare const storage: AsyncLocalStorage<RequestStore>;
16
+ declare const currentStore: () => RequestStore | undefined;
17
+ /**
18
+ * Makes `client.scope` and `client.activeTransaction` follow the request that is running, and fall back to the
19
+ * process-wide ones outside any request. Applied to the instance, so core stays free of Node.
20
+ */
21
+ declare function isolatePerRequest(client: Client): void;
22
+ /** Runs `fn` as one request: a copy of the process scope, its own transaction slot, and the request being served. */
23
+ declare function runWithRequest<T>(client: Client, request: MagpieEvent['request'] | undefined, fn: () => T): T;
24
+
25
+ /**
26
+ * Reports what the process did not catch. Adding a listener for either event removes Node's own reaction to it (print
27
+ * and exit), so after reporting, that reaction is restored unless something else in the process is handling it.
28
+ */
29
+ declare function processHandlers(): Integration;
30
+
31
+ /**
32
+ * Times the server's own outgoing `fetch` calls (an API your pages render from). Each becomes a breadcrumb on the
33
+ * current request and, when that request is being traced, a span of it, and carries the `magpie-trace` header so
34
+ * the service on the other end continues the trace: browser, Nuxt server and Laravel API as one.
35
+ *
36
+ * The header goes to every host unless `tracePropagationTargets` lists the ones that should get it.
37
+ */
38
+ declare function nodeHttp(): Integration;
39
+
40
+ interface RequestInfo {
41
+ method: string;
42
+ /** The path (and query string), or a full URL. */
43
+ url: string;
44
+ /** The request's headers, lower-case names. Only a short allow-list of them is ever attached to an event. */
45
+ headers?: Record<string, string | string[] | undefined>;
46
+ /** The matched route pattern (`/users/:id`) when the framework already knows it. */
47
+ route?: string;
48
+ }
49
+ interface RequestHandle {
50
+ transaction: Transaction;
51
+ /** Ends the request's transaction. Pass the route pattern when it is only known now, so `/users/7` becomes `/users/:id`. */
52
+ finish(statusCode: number, route?: string): void;
53
+ }
54
+ /**
55
+ * Runs `fn` as one request: its own scope and transaction, the request attached to every event raised inside it, and a
56
+ * trace that continues the caller's `magpie-trace` header (so a page load in the browser and this server render are one
57
+ * trace). Call `finish` when the response is done.
58
+ */
59
+ declare function trackRequest<T>(client: Client, info: RequestInfo, fn: (handle: RequestHandle) => T): T;
60
+
61
+ /**
62
+ * Delivers payloads from a server. There is no CORS to avoid here, so the key goes in the `X-Magpie-Auth` header,
63
+ * the same as the Laravel SDK, and the body is JSON, compressed when it is large.
64
+ */
65
+ declare function nodeTransport(dsn: Dsn, fetchImpl?: typeof fetch, timeoutMs?: number): Transport;
66
+
67
+ interface NodeOptions extends Options {
68
+ /** Report uncaught exceptions and unhandled rejections. Default true. */
69
+ captureUnhandled?: boolean;
70
+ /** Time and propagate the server's own `fetch` calls. Default true. */
71
+ http?: boolean;
72
+ sdkName?: string;
73
+ }
74
+ /**
75
+ * Starts Magpie on a server. Per-request data (scope, transaction, the request) is isolated with AsyncLocalStorage, so
76
+ * use `trackRequest` around each request the framework handles.
77
+ */
78
+ declare function init(options?: NodeOptions): Client;
79
+
80
+ export { type NodeOptions, type RequestHandle, type RequestInfo, currentStore, init, isolatePerRequest, nodeHttp, nodeTransport, processHandlers, runWithRequest, storage, trackRequest };
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ import { scrubUrl, TRACE_HEADER, formatTraceHeader, endpoint, scrubQuery, parseTraceHeader, getClient, Client, setClient, SDK_VERSION } from '@greenhat-services/magpie-core';
2
+ export * from '@greenhat-services/magpie-core';
3
+ import { AsyncLocalStorage } from 'async_hooks';
4
+ import { hostname, release, platform } from 'os';
5
+ import { gzipSync } from 'zlib';
6
+
7
+ // src/index.ts
8
+ var storage = new AsyncLocalStorage();
9
+ var currentStore = () => storage.getStore();
10
+ function isolatePerRequest(client) {
11
+ let processScope = client.scope;
12
+ let processTransaction = client.activeTransaction;
13
+ Object.defineProperty(client, "scope", {
14
+ configurable: true,
15
+ get: () => storage.getStore()?.scope ?? processScope,
16
+ // withScope() swaps in a copy while its callback runs and puts the original back afterwards. Inside a request
17
+ // that swap belongs to the request, so one request's temporary tags can never reach another.
18
+ set: (scope) => {
19
+ const store = storage.getStore();
20
+ if (store) store.scope = scope;
21
+ else processScope = scope;
22
+ }
23
+ });
24
+ Object.defineProperty(client, "activeTransaction", {
25
+ configurable: true,
26
+ get: () => {
27
+ const store = storage.getStore();
28
+ return store ? store.transaction : processTransaction;
29
+ },
30
+ set: (tx) => {
31
+ const store = storage.getStore();
32
+ if (store) store.transaction = tx;
33
+ else processTransaction = tx;
34
+ }
35
+ });
36
+ }
37
+ function runWithRequest(client, request, fn) {
38
+ return storage.run({ scope: client.scope.clone(), transaction: void 0, request }, fn);
39
+ }
40
+
41
+ // src/handlers.ts
42
+ function processHandlers() {
43
+ return {
44
+ name: "ProcessHandlers",
45
+ setup(client) {
46
+ const onUncaught = (error) => {
47
+ client.captureException(error, { mechanism: { type: "onuncaughtexception", handled: false } });
48
+ void client.flush(2e3).finally(() => {
49
+ if (process.listenerCount("uncaughtException") <= 1) {
50
+ console.error(error);
51
+ process.exit(1);
52
+ }
53
+ });
54
+ };
55
+ const onRejection = (reason) => {
56
+ client.captureException(reason, { mechanism: { type: "onunhandledrejection", handled: false } });
57
+ if (process.listenerCount("unhandledRejection") <= 1) {
58
+ setImmediate(() => {
59
+ throw reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${String(reason)}`);
60
+ });
61
+ }
62
+ };
63
+ process.on("uncaughtException", onUncaught);
64
+ process.on("unhandledRejection", onRejection);
65
+ return () => {
66
+ process.off("uncaughtException", onUncaught);
67
+ process.off("unhandledRejection", onRejection);
68
+ };
69
+ }
70
+ };
71
+ }
72
+ function nodeHttp() {
73
+ return {
74
+ name: "NodeHttp",
75
+ setup(client) {
76
+ if (typeof globalThis.fetch !== "function") return;
77
+ const original = globalThis.fetch;
78
+ const skip = (url) => !!client.dsn && url.startsWith(client.dsn.origin);
79
+ globalThis.fetch = function patchedFetch(input, init2) {
80
+ const isRequest = typeof Request !== "undefined" && input instanceof Request;
81
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : isRequest ? input.url : String(input);
82
+ const method = (init2?.method ?? (isRequest ? input.method : "GET")).toUpperCase();
83
+ if (skip(url)) return original.call(this, input, init2);
84
+ const tx = client.activeTransaction;
85
+ const started = performance.now();
86
+ let span;
87
+ if (tx?.sampled) span = tx.startChild({ op: "http.client", description: `${method} ${scrubUrl(url).split(/[?#]/)[0]}`, data: { method } });
88
+ const propagate = (client.options.tracesSampleRate ?? 0) > 0 && !!tx && (client.options.tracePropagationTargets === void 0 || client.shouldPropagate(url));
89
+ let args = [input, init2];
90
+ if (propagate && tx) {
91
+ const headers = new Headers(init2?.headers ?? (isRequest ? input.headers : void 0));
92
+ if (!headers.has(TRACE_HEADER)) headers.set(TRACE_HEADER, formatTraceHeader(tx.traceId, span?.spanId ?? tx.spanId, tx.sampled));
93
+ args = [input, { ...init2, headers }];
94
+ }
95
+ const finish = (status) => {
96
+ if (span) {
97
+ if (status) span.setData("status_code", status);
98
+ span.setStatus(status === 0 ? "unknown_error" : status >= 500 ? "internal_error" : "ok");
99
+ span.end();
100
+ }
101
+ client.addBreadcrumb({
102
+ category: "http",
103
+ type: "http",
104
+ level: status >= 500 || status === 0 ? "error" : status >= 400 ? "warning" : "info",
105
+ message: `${method} ${scrubUrl(url)}`,
106
+ data: { method, status_code: status || "failed", duration_ms: Math.round(performance.now() - started) }
107
+ });
108
+ };
109
+ return original.apply(this, args).then(
110
+ (response) => {
111
+ finish(response.status);
112
+ return response;
113
+ },
114
+ (error) => {
115
+ finish(0);
116
+ throw error;
117
+ }
118
+ );
119
+ };
120
+ return () => {
121
+ globalThis.fetch = original;
122
+ };
123
+ }
124
+ };
125
+ }
126
+ var COMPRESS_FROM = 4096;
127
+ function nodeTransport(dsn, fetchImpl = (...args) => globalThis.fetch(...args), timeoutMs = 3e3) {
128
+ return {
129
+ async send(payload) {
130
+ try {
131
+ const path = payload.type === "transaction" ? "transactions" : "events";
132
+ const json = JSON.stringify(payload);
133
+ const compressed = json.length >= COMPRESS_FROM;
134
+ const response = await fetchImpl(endpoint(dsn, path), {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ "X-Magpie-Auth": `Magpie magpie_version=1, magpie_key=${dsn.publicKey}, magpie_client=magpie-node`,
139
+ ...compressed ? { "Content-Encoding": "gzip" } : {}
140
+ },
141
+ body: compressed ? new Uint8Array(gzipSync(json)) : json,
142
+ signal: AbortSignal.timeout(timeoutMs)
143
+ });
144
+ const retry = Number(response.headers.get("retry-after"));
145
+ return { ok: response.ok, status: response.status, retryAfter: Number.isFinite(retry) && retry > 0 ? Math.min(retry, 3600) : void 0 };
146
+ } catch {
147
+ return { ok: false };
148
+ }
149
+ }
150
+ };
151
+ }
152
+
153
+ // src/runtime.ts
154
+ var SDK_NAME = "magpie.javascript.node";
155
+ var OS_NAMES = { win32: "Windows", darwin: "macOS", linux: "Linux", freebsd: "FreeBSD" };
156
+ function nodeRuntime(options = {}) {
157
+ return {
158
+ sdk: { name: options.sdkName ?? SDK_NAME, version: SDK_VERSION },
159
+ platform: "node",
160
+ createTransport: (dsn) => nodeTransport(dsn, options.fetch),
161
+ contexts: () => ({
162
+ runtime: { name: "node", version: process.version.replace(/^v/, "") },
163
+ os: { name: OS_NAMES[platform()] ?? platform(), version: release() }
164
+ }),
165
+ // The request being served, when there is one: set by `trackRequest`, read here for every event.
166
+ request: () => currentStore()?.request
167
+ };
168
+ }
169
+ var defaultServerName = () => {
170
+ try {
171
+ return hostname() || void 0;
172
+ } catch {
173
+ return void 0;
174
+ }
175
+ };
176
+ var SAFE_HEADERS = ["user-agent", "accept", "accept-language", "content-type", "host", "referer", "x-forwarded-proto"];
177
+ function trackRequest(client, info, fn) {
178
+ const { path, query } = splitUrl(info.url);
179
+ const request = {
180
+ url: info.headers?.host ? `${firstHeader(info.headers["x-forwarded-proto"]) ?? "http"}://${firstHeader(info.headers.host)}${path}` : path,
181
+ method: info.method.toUpperCase(),
182
+ ...query ? { query_string: scrubQuery(query) } : {},
183
+ headers: safeHeaders(info.headers)
184
+ };
185
+ return runWithRequest(client, request, () => {
186
+ const parent = parseTraceHeader(firstHeader(info.headers?.[TRACE_HEADER]));
187
+ const method = info.method.toUpperCase();
188
+ const name = (route) => `${method} ${route ?? info.route ?? path}`;
189
+ const transaction = client.startTransaction({
190
+ name: name(),
191
+ op: "http.server",
192
+ ...parent ? { traceId: parent.traceId, parentSpanId: parent.spanId, sampled: parent.sampled } : {}
193
+ });
194
+ client.activeTransaction = transaction;
195
+ client.scope.setTransactionName(transaction.name);
196
+ const finish = (statusCode, route) => {
197
+ transaction.setName(name(route));
198
+ transaction.setData("http.status_code", statusCode);
199
+ if (statusCode >= 500) transaction.setStatus("internal_error");
200
+ transaction.end();
201
+ };
202
+ return fn({ transaction, finish });
203
+ });
204
+ }
205
+ function splitUrl(url) {
206
+ let target = url;
207
+ try {
208
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
209
+ const u = new URL(url);
210
+ target = `${u.pathname}${u.search}`;
211
+ }
212
+ } catch {
213
+ }
214
+ const q = target.indexOf("?");
215
+ return q < 0 ? { path: target, query: "" } : { path: target.slice(0, q), query: target.slice(q + 1) };
216
+ }
217
+ function firstHeader(value) {
218
+ return Array.isArray(value) ? value[0] : value;
219
+ }
220
+ function safeHeaders(headers) {
221
+ const out = {};
222
+ for (const name of SAFE_HEADERS) {
223
+ const value = firstHeader(headers?.[name]);
224
+ if (value) out[name] = value;
225
+ }
226
+ return out;
227
+ }
228
+
229
+ // src/index.ts
230
+ function init(options = {}) {
231
+ const previous = getClient();
232
+ if (previous) void previous.close(0);
233
+ const { captureUnhandled = true, http = true, sdkName, integrations = [], ...rest } = options;
234
+ const client = new Client(
235
+ { serverName: defaultServerName(), ...rest, release: rest.release ?? process.env.MAGPIE_RELEASE },
236
+ nodeRuntime({ sdkName })
237
+ );
238
+ isolatePerRequest(client);
239
+ setClient(client);
240
+ const defaults = [...captureUnhandled ? [processHandlers()] : [], ...http ? [nodeHttp()] : []];
241
+ client.setupIntegrations([...defaults, ...integrations]);
242
+ return client;
243
+ }
244
+
245
+ export { currentStore, init, isolatePerRequest, nodeHttp, nodeTransport, processHandlers, runWithRequest, storage, trackRequest };
246
+ //# sourceMappingURL=index.js.map
247
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts","../src/handlers.ts","../src/http.ts","../src/transport.ts","../src/runtime.ts","../src/request.ts","../src/index.ts"],"names":["init","TRACE_HEADER"],"mappings":";;;;;;;AAcO,IAAM,OAAA,GAAU,IAAI,iBAAA;AAEpB,IAAM,YAAA,GAAe,MAAgC,OAAA,CAAQ,QAAA;AAM7D,SAAS,kBAAkB,MAAA,EAAsB;AACpD,EAAA,IAAI,eAAe,MAAA,CAAO,KAAA;AAC1B,EAAA,IAAI,qBAAqB,MAAA,CAAO,iBAAA;AAEhC,EAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,OAAA,EAAS;AAAA,IACnC,YAAA,EAAc,IAAA;AAAA,IACd,GAAA,EAAK,MAAM,OAAA,CAAQ,QAAA,IAAY,KAAA,IAAS,YAAA;AAAA;AAAA;AAAA,IAGxC,GAAA,EAAK,CAAC,KAAA,KAAiB;AACnB,MAAA,MAAM,KAAA,GAAQ,QAAQ,QAAA,EAAS;AAC/B,MAAA,IAAI,KAAA,QAAa,KAAA,GAAQ,KAAA;AAAA,WACpB,YAAA,GAAe,KAAA;AAAA,IACxB;AAAA,GACH,CAAA;AAED,EAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,mBAAA,EAAqB;AAAA,IAC/C,YAAA,EAAc,IAAA;AAAA,IACd,KAAK,MAAM;AACP,MAAA,MAAM,KAAA,GAAQ,QAAQ,QAAA,EAAS;AAE/B,MAAA,OAAO,KAAA,GAAQ,MAAM,WAAA,GAAc,kBAAA;AAAA,IACvC,CAAA;AAAA,IACA,GAAA,EAAK,CAAC,EAAA,KAAgC;AAClC,MAAA,MAAM,KAAA,GAAQ,QAAQ,QAAA,EAAS;AAC/B,MAAA,IAAI,KAAA,QAAa,WAAA,GAAc,EAAA;AAAA,WAC1B,kBAAA,GAAqB,EAAA;AAAA,IAC9B;AAAA,GACH,CAAA;AACL;AAGO,SAAS,cAAA,CAAkB,MAAA,EAAgB,OAAA,EAA6C,EAAA,EAAgB;AAC3G,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,EAAE,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,KAAA,EAAM,EAAG,WAAA,EAAa,MAAA,EAAW,OAAA,EAAQ,EAAG,EAAE,CAAA;AAC3F;;;AClDO,SAAS,eAAA,GAA+B;AAC3C,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,iBAAA;AAAA,IACN,MAAM,MAAA,EAAgB;AAClB,MAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAiB;AACjC,QAAA,MAAA,CAAO,gBAAA,CAAiB,KAAA,EAAO,EAAE,SAAA,EAAW,EAAE,MAAM,qBAAA,EAAuB,OAAA,EAAS,KAAA,EAAM,EAAG,CAAA;AAE7F,QAAA,KAAK,MAAA,CAAO,KAAA,CAAM,GAAI,CAAA,CAAE,QAAQ,MAAM;AAClC,UAAA,IAAI,OAAA,CAAQ,aAAA,CAAc,mBAAmB,CAAA,IAAK,CAAA,EAAG;AACjD,YAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AACnB,YAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,UAClB;AAAA,QACJ,CAAC,CAAA;AAAA,MACL,CAAA;AAEA,MAAA,MAAM,WAAA,GAAc,CAAC,MAAA,KAAoB;AACrC,QAAA,MAAA,CAAO,gBAAA,CAAiB,MAAA,EAAQ,EAAE,SAAA,EAAW,EAAE,MAAM,sBAAA,EAAwB,OAAA,EAAS,KAAA,EAAM,EAAG,CAAA;AAI/F,QAAA,IAAI,OAAA,CAAQ,aAAA,CAAc,oBAAoB,CAAA,IAAK,CAAA,EAAG;AAClD,UAAA,YAAA,CAAa,MAAM;AACf,YAAA,MAAM,MAAA,YAAkB,QAAQ,MAAA,GAAS,IAAI,MAAM,CAAA,6BAAA,EAAgC,MAAA,CAAO,MAAM,CAAC,CAAA,CAAE,CAAA;AAAA,UACvG,CAAC,CAAA;AAAA,QACL;AAAA,MACJ,CAAA;AAEA,MAAA,OAAA,CAAQ,EAAA,CAAG,qBAAqB,UAAU,CAAA;AAC1C,MAAA,OAAA,CAAQ,EAAA,CAAG,sBAAsB,WAAW,CAAA;AAE5C,MAAA,OAAO,MAAM;AACT,QAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,UAAU,CAAA;AAC3C,QAAA,OAAA,CAAQ,GAAA,CAAI,sBAAsB,WAAW,CAAA;AAAA,MACjD,CAAA;AAAA,IACJ;AAAA,GACJ;AACJ;ACjCO,SAAS,QAAA,GAAwB;AACpC,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,UAAA;AAAA,IACN,MAAM,MAAA,EAAgB;AAClB,MAAA,IAAI,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AAE5C,MAAA,MAAM,WAAW,UAAA,CAAW,KAAA;AAC5B,MAAA,MAAM,IAAA,GAAO,CAAC,GAAA,KAAgB,CAAC,CAAC,MAAA,CAAO,GAAA,IAAO,GAAA,CAAI,UAAA,CAAW,MAAA,CAAO,GAAA,CAAI,MAAM,CAAA;AAE9E,MAAA,UAAA,CAAW,KAAA,GAAQ,SAAS,YAAA,CAA4B,KAAA,EAAoCA,KAAAA,EAAoB;AAC5G,QAAA,MAAM,SAAA,GAAY,OAAO,OAAA,KAAY,WAAA,IAAe,KAAA,YAAiB,OAAA;AACrE,QAAA,MAAM,GAAA,GAAM,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,KAAA,YAAiB,GAAA,GAAM,KAAA,CAAM,IAAA,GAAO,SAAA,GAAa,KAAA,CAAkB,GAAA,GAAM,OAAO,KAAK,CAAA;AACrI,QAAA,MAAM,UAAUA,KAAAA,EAAM,MAAA,KAAW,YAAa,KAAA,CAAkB,MAAA,GAAS,QAAQ,WAAA,EAAY;AAE7F,QAAA,IAAI,IAAA,CAAK,GAAG,CAAA,EAAG,OAAO,SAAS,IAAA,CAAK,IAAA,EAAM,OAAOA,KAAI,CAAA;AAErD,QAAA,MAAM,KAAK,MAAA,CAAO,iBAAA;AAClB,QAAA,MAAM,OAAA,GAAU,YAAY,GAAA,EAAI;AAChC,QAAA,IAAI,IAAA;AACJ,QAAA,IAAI,EAAA,EAAI,OAAA,EAAS,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,EAAE,EAAA,EAAI,aAAA,EAAe,WAAA,EAAa,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,SAAS,GAAG,CAAA,CAAE,KAAA,CAAM,MAAM,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA,EAAI,IAAA,EAAM,EAAE,MAAA,EAAO,EAAG,CAAA;AAEzI,QAAA,MAAM,SAAA,GAAA,CAAa,MAAA,CAAO,OAAA,CAAQ,gBAAA,IAAoB,KAAK,CAAA,IAAK,CAAC,CAAC,EAAA,KAAO,OAAO,OAAA,CAAQ,uBAAA,KAA4B,MAAA,IAAa,MAAA,CAAO,gBAAgB,GAAG,CAAA,CAAA;AAC3J,QAAA,IAAI,IAAA,GAA+D,CAAC,KAAA,EAAOA,KAAI,CAAA;AAC/E,QAAA,IAAI,aAAa,EAAA,EAAI;AACjB,UAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQA,KAAAA,EAAM,YAAY,SAAA,GAAa,KAAA,CAAkB,UAAU,MAAA,CAAU,CAAA;AACjG,UAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,UAAW,GAAA,CAAI,YAAA,EAAc,iBAAA,CAAkB,EAAA,CAAG,SAAS,IAAA,EAAM,MAAA,IAAU,GAAG,MAAA,EAAQ,EAAA,CAAG,OAAO,CAAC,CAAA;AAC9H,UAAA,IAAA,GAAO,CAAC,KAAA,EAAO,EAAE,GAAGA,KAAAA,EAAM,SAAS,CAAA;AAAA,QACvC;AAEA,QAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAAmB;AAC/B,UAAA,IAAI,IAAA,EAAM;AACN,YAAA,IAAI,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,aAAA,EAAe,MAAM,CAAA;AAC9C,YAAA,IAAA,CAAK,UAAU,MAAA,KAAW,CAAA,GAAI,kBAAkB,MAAA,IAAU,GAAA,GAAM,mBAAmB,IAAI,CAAA;AACvF,YAAA,IAAA,CAAK,GAAA,EAAI;AAAA,UACb;AACA,UAAA,MAAA,CAAO,aAAA,CAAc;AAAA,YACjB,QAAA,EAAU,MAAA;AAAA,YACV,IAAA,EAAM,MAAA;AAAA,YACN,KAAA,EAAO,UAAU,GAAA,IAAO,MAAA,KAAW,IAAI,OAAA,GAAU,MAAA,IAAU,MAAM,SAAA,GAAY,MAAA;AAAA,YAC7E,SAAS,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,GAAG,CAAC,CAAA,CAAA;AAAA,YACnC,IAAA,EAAM,EAAE,MAAA,EAAQ,WAAA,EAAa,MAAA,IAAU,QAAA,EAAU,WAAA,EAAa,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,EAAI,GAAI,OAAO,CAAA;AAAE,WACzG,CAAA;AAAA,QACL,CAAA;AAEA,QAAA,OAAO,QAAA,CAAS,KAAA,CAAM,IAAA,EAAM,IAAI,CAAA,CAAE,IAAA;AAAA,UAC9B,CAAC,QAAA,KAAa;AACV,YAAA,MAAA,CAAO,SAAS,MAAM,CAAA;AAEtB,YAAA,OAAO,QAAA;AAAA,UACX,CAAA;AAAA,UACA,CAAC,KAAA,KAAU;AACP,YAAA,MAAA,CAAO,CAAC,CAAA;AACR,YAAA,MAAM,KAAA;AAAA,UACV;AAAA,SACJ;AAAA,MACJ,CAAA;AAEA,MAAA,OAAO,MAAM;AACT,QAAA,UAAA,CAAW,KAAA,GAAQ,QAAA;AAAA,MACvB,CAAA;AAAA,IACJ;AAAA,GACJ;AACJ;ACnEA,IAAM,aAAA,GAAgB,IAAA;AAMf,SAAS,aAAA,CAAc,GAAA,EAAU,SAAA,GAA0B,CAAA,GAAI,IAAA,KAAS,UAAA,CAAW,KAAA,CAAM,GAAG,IAAI,CAAA,EAAG,SAAA,GAAY,GAAA,EAAiB;AACnI,EAAA,OAAO;AAAA,IACH,MAAM,KAAK,OAAA,EAA4C;AACnD,MAAA,IAAI;AACA,QAAA,MAAM,IAAA,GAAQ,OAAA,CAA8B,IAAA,KAAS,aAAA,GAAgB,cAAA,GAAiB,QAAA;AACtF,QAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AACnC,QAAA,MAAM,UAAA,GAAa,KAAK,MAAA,IAAU,aAAA;AAElC,QAAA,MAAM,WAAW,MAAM,SAAA,CAAU,QAAA,CAAS,GAAA,EAAK,IAAI,CAAA,EAAG;AAAA,UAClD,MAAA,EAAQ,MAAA;AAAA,UACR,OAAA,EAAS;AAAA,YACL,cAAA,EAAgB,kBAAA;AAAA,YAChB,eAAA,EAAiB,CAAA,oCAAA,EAAuC,GAAA,CAAI,SAAS,CAAA,2BAAA,CAAA;AAAA,YACrE,GAAI,UAAA,GAAa,EAAE,kBAAA,EAAoB,MAAA,KAAW;AAAC,WACvD;AAAA,UACA,MAAM,UAAA,GAAa,IAAI,WAAW,QAAA,CAAS,IAAI,CAAC,CAAA,GAAI,IAAA;AAAA,UACpD,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,SAAS;AAAA,SACxC,CAAA;AAED,QAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAC,CAAA;AAExD,QAAA,OAAO,EAAE,EAAA,EAAI,QAAA,CAAS,IAAI,MAAA,EAAQ,QAAA,CAAS,QAAQ,UAAA,EAAY,MAAA,CAAO,SAAS,KAAK,CAAA,IAAK,QAAQ,CAAA,GAAI,IAAA,CAAK,IAAI,KAAA,EAAO,IAAI,IAAI,KAAA,CAAA,EAAU;AAAA,MAC3I,CAAA,CAAA,MAAQ;AAEJ,QAAA,OAAO,EAAE,IAAI,KAAA,EAAM;AAAA,MACvB;AAAA,IACJ;AAAA,GACJ;AACJ;;;ACjCO,IAAM,QAAA,GAAW,wBAAA;AAExB,IAAM,QAAA,GAAmC,EAAE,KAAA,EAAO,SAAA,EAAW,QAAQ,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,OAAA,EAAS,SAAA,EAAU;AAE1G,SAAS,WAAA,CAAY,OAAA,GAAsD,EAAC,EAAY;AAC3F,EAAA,OAAO;AAAA,IACH,KAAK,EAAE,IAAA,EAAM,QAAQ,OAAA,IAAW,QAAA,EAAU,SAAS,WAAA,EAAY;AAAA,IAC/D,QAAA,EAAU,MAAA;AAAA,IACV,iBAAiB,CAAC,GAAA,KAAQ,aAAA,CAAc,GAAA,EAAK,QAAQ,KAAK,CAAA;AAAA,IAC1D,UAAU,OAAO;AAAA,MACb,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,QAAQ,OAAA,CAAQ,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA,EAAE;AAAA,MACpE,EAAA,EAAI,EAAE,IAAA,EAAM,QAAA,CAAS,QAAA,EAAU,CAAA,IAAK,QAAA,EAAS,EAAG,OAAA,EAAS,OAAA,EAAQ;AAAE,KACvE,CAAA;AAAA;AAAA,IAEA,OAAA,EAAS,MAAM,YAAA,EAAa,EAAG;AAAA,GACnC;AACJ;AAEO,IAAM,oBAAoB,MAA0B;AACvD,EAAA,IAAI;AACA,IAAA,OAAO,UAAS,IAAK,KAAA,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,MAAA;AAAA,EACX;AACJ,CAAA;ACfA,IAAM,YAAA,GAAe,CAAC,YAAA,EAAc,QAAA,EAAU,mBAAmB,cAAA,EAAgB,MAAA,EAAQ,WAAW,mBAAmB,CAAA;AAahH,SAAS,YAAA,CAAgB,MAAA,EAAgB,IAAA,EAAmB,EAAA,EAAqC;AACpG,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,QAAA,CAAS,KAAK,GAAG,CAAA;AACzC,EAAA,MAAM,OAAA,GAAU;AAAA,IACZ,GAAA,EAAK,KAAK,OAAA,EAAS,IAAA,GAAO,GAAG,WAAA,CAAY,IAAA,CAAK,QAAQ,mBAAmB,CAAC,KAAK,MAAM,CAAA,GAAA,EAAM,YAAY,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,EAAG,IAAI,CAAA,CAAA,GAAK,IAAA;AAAA,IACrI,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,WAAA,EAAY;AAAA,IAChC,GAAI,QAAQ,EAAE,YAAA,EAAc,WAAW,KAAK,CAAA,KAAM,EAAC;AAAA,IACnD,OAAA,EAAS,WAAA,CAAY,IAAA,CAAK,OAAO;AAAA,GACrC;AAEA,EAAA,OAAO,cAAA,CAAe,MAAA,EAAQ,OAAA,EAAS,MAAM;AACzC,IAAA,MAAM,SAAS,gBAAA,CAAiB,WAAA,CAAY,KAAK,OAAA,GAAUC,YAAY,CAAC,CAAC,CAAA;AACzE,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,WAAA,EAAY;AACvC,IAAA,MAAM,IAAA,GAAO,CAAC,KAAA,KAAmB,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,KAAA,IAAS,IAAA,CAAK,KAAA,IAAS,IAAI,CAAA,CAAA;AAEzE,IAAA,MAAM,WAAA,GAAc,OAAO,gBAAA,CAAiB;AAAA,MACxC,MAAM,IAAA,EAAK;AAAA,MACX,EAAA,EAAI,aAAA;AAAA,MACJ,GAAI,MAAA,GAAS,EAAE,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,YAAA,EAAc,MAAA,CAAO,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,KAAY;AAAC,KACrG,CAAA;AACD,IAAA,MAAA,CAAO,iBAAA,GAAoB,WAAA;AAC3B,IAAA,MAAA,CAAO,KAAA,CAAM,kBAAA,CAAmB,WAAA,CAAY,IAAI,CAAA;AAEhD,IAAA,MAAM,MAAA,GAAS,CAAC,UAAA,EAAoB,KAAA,KAAmB;AACnD,MAAA,WAAA,CAAY,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AAC/B,MAAA,WAAA,CAAY,OAAA,CAAQ,oBAAoB,UAAU,CAAA;AAElD,MAAA,IAAI,UAAA,IAAc,GAAA,EAAK,WAAA,CAAY,SAAA,CAAU,gBAAgB,CAAA;AAC7D,MAAA,WAAA,CAAY,GAAA,EAAI;AAAA,IACpB,CAAA;AAEA,IAAA,OAAO,EAAA,CAAG,EAAE,WAAA,EAAa,MAAA,EAAQ,CAAA;AAAA,EACrC,CAAC,CAAA;AACL;AAEA,SAAS,SAAS,GAAA,EAA8C;AAC5D,EAAA,IAAI,MAAA,GAAS,GAAA;AACb,EAAA,IAAI;AACA,IAAA,IAAI,sBAAA,CAAuB,IAAA,CAAK,GAAG,CAAA,EAAG;AAClC,MAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,GAAG,CAAA;AACrB,MAAA,MAAA,GAAS,CAAA,EAAG,CAAA,CAAE,QAAQ,CAAA,EAAG,EAAE,MAAM,CAAA,CAAA;AAAA,IACrC;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAE5B,EAAA,OAAO,CAAA,GAAI,IAAI,EAAE,IAAA,EAAM,QAAQ,KAAA,EAAO,EAAA,KAAO,EAAE,IAAA,EAAM,OAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG,KAAA,EAAO,OAAO,KAAA,CAAM,CAAA,GAAI,CAAC,CAAA,EAAE;AACxG;AAEA,SAAS,YAAY,KAAA,EAA0D;AAC3E,EAAA,OAAO,MAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,CAAM,CAAC,CAAA,GAAI,KAAA;AAC7C;AAEA,SAAS,YAAY,OAAA,EAAyD;AAC1E,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,QAAQ,YAAA,EAAc;AAC7B,IAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,OAAA,GAAU,IAAI,CAAC,CAAA;AACzC,IAAA,IAAI,KAAA,EAAO,GAAA,CAAI,IAAI,CAAA,GAAI,KAAA;AAAA,EAC3B;AAEA,EAAA,OAAO,GAAA;AACX;;;AC/DO,SAAS,IAAA,CAAK,OAAA,GAAuB,EAAC,EAAW;AACpD,EAAA,MAAM,WAAW,SAAA,EAAU;AAC3B,EAAA,IAAI,QAAA,EAAU,KAAK,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AAEnC,EAAA,MAAM,EAAE,gBAAA,GAAmB,IAAA,EAAM,IAAA,GAAO,IAAA,EAAM,OAAA,EAAS,YAAA,GAAe,EAAC,EAAG,GAAG,IAAA,EAAK,GAAI,OAAA;AAEtF,EAAA,MAAM,SAAS,IAAI,MAAA;AAAA,IACf,EAAE,UAAA,EAAY,iBAAA,EAAkB,EAAG,GAAG,IAAA,EAAM,OAAA,EAAS,IAAA,CAAK,OAAA,IAAW,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAe;AAAA,IAChG,WAAA,CAAY,EAAE,OAAA,EAAS;AAAA,GAC3B;AAEA,EAAA,iBAAA,CAAkB,MAAM,CAAA;AACxB,EAAA,SAAA,CAAU,MAAM,CAAA;AAEhB,EAAA,MAAM,WAA0B,CAAC,GAAI,gBAAA,GAAmB,CAAC,iBAAiB,CAAA,GAAI,EAAC,EAAI,GAAI,IAAA,GAAO,CAAC,UAAU,CAAA,GAAI,EAAG,CAAA;AAChH,EAAA,MAAA,CAAO,kBAAkB,CAAC,GAAG,QAAA,EAAU,GAAG,YAAY,CAAC,CAAA;AAEvD,EAAA,OAAO,MAAA;AACX","file":"index.js","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Client, MagpieEvent, Scope, Transaction } from '@greenhat-services/magpie-core';\n\n/**\n * What belongs to one request on a server that handles many at once: its own scope (tags, user, breadcrumbs), the\n * transaction being timed, and the request being served. Carried through every `await` by AsyncLocalStorage, so two\n * overlapping requests never see each other's data.\n */\nexport interface RequestStore {\n scope: Scope;\n transaction: Transaction | undefined;\n request: MagpieEvent['request'] | undefined;\n}\n\nexport const storage = new AsyncLocalStorage<RequestStore>();\n\nexport const currentStore = (): RequestStore | undefined => storage.getStore();\n\n/**\n * Makes `client.scope` and `client.activeTransaction` follow the request that is running, and fall back to the\n * process-wide ones outside any request. Applied to the instance, so core stays free of Node.\n */\nexport function isolatePerRequest(client: Client): void {\n let processScope = client.scope;\n let processTransaction = client.activeTransaction;\n\n Object.defineProperty(client, 'scope', {\n configurable: true,\n get: () => storage.getStore()?.scope ?? processScope,\n // withScope() swaps in a copy while its callback runs and puts the original back afterwards. Inside a request\n // that swap belongs to the request, so one request's temporary tags can never reach another.\n set: (scope: Scope) => {\n const store = storage.getStore();\n if (store) store.scope = scope;\n else processScope = scope;\n },\n });\n\n Object.defineProperty(client, 'activeTransaction', {\n configurable: true,\n get: () => {\n const store = storage.getStore();\n\n return store ? store.transaction : processTransaction;\n },\n set: (tx: Transaction | undefined) => {\n const store = storage.getStore();\n if (store) store.transaction = tx;\n else processTransaction = tx;\n },\n });\n}\n\n/** Runs `fn` as one request: a copy of the process scope, its own transaction slot, and the request being served. */\nexport function runWithRequest<T>(client: Client, request: MagpieEvent['request'] | undefined, fn: () => T): T {\n return storage.run({ scope: client.scope.clone(), transaction: undefined, request }, fn);\n}\n","import type { Client, Integration } from '@greenhat-services/magpie-core';\n\n/**\n * Reports what the process did not catch. Adding a listener for either event removes Node's own reaction to it (print\n * and exit), so after reporting, that reaction is restored unless something else in the process is handling it.\n */\nexport function processHandlers(): Integration {\n return {\n name: 'ProcessHandlers',\n setup(client: Client) {\n const onUncaught = (error: Error) => {\n client.captureException(error, { mechanism: { type: 'onuncaughtexception', handled: false } });\n\n void client.flush(2000).finally(() => {\n if (process.listenerCount('uncaughtException') <= 1) {\n console.error(error);\n process.exit(1);\n }\n });\n };\n\n const onRejection = (reason: unknown) => {\n client.captureException(reason, { mechanism: { type: 'onunhandledrejection', handled: false } });\n\n // Node's default since v15 is to raise an unhandled rejection as an uncaught exception. Keep it, so a\n // server that crashed before Magpie still crashes (and the crash is already reported).\n if (process.listenerCount('unhandledRejection') <= 1) {\n setImmediate(() => {\n throw reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${String(reason)}`);\n });\n }\n };\n\n process.on('uncaughtException', onUncaught);\n process.on('unhandledRejection', onRejection);\n\n return () => {\n process.off('uncaughtException', onUncaught);\n process.off('unhandledRejection', onRejection);\n };\n },\n };\n}\n","import { type Client, formatTraceHeader, type Integration, scrubUrl, type Span, TRACE_HEADER } from '@greenhat-services/magpie-core';\n\n/**\n * Times the server's own outgoing `fetch` calls (an API your pages render from). Each becomes a breadcrumb on the\n * current request and, when that request is being traced, a span of it, and carries the `magpie-trace` header so\n * the service on the other end continues the trace: browser, Nuxt server and Laravel API as one.\n *\n * The header goes to every host unless `tracePropagationTargets` lists the ones that should get it.\n */\nexport function nodeHttp(): Integration {\n return {\n name: 'NodeHttp',\n setup(client: Client) {\n if (typeof globalThis.fetch !== 'function') return;\n\n const original = globalThis.fetch;\n const skip = (url: string) => !!client.dsn && url.startsWith(client.dsn.origin);\n\n globalThis.fetch = function patchedFetch(this: unknown, input: Parameters<typeof fetch>[0], init?: RequestInit) {\n const isRequest = typeof Request !== 'undefined' && input instanceof Request;\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : isRequest ? (input as Request).url : String(input);\n const method = (init?.method ?? (isRequest ? (input as Request).method : 'GET')).toUpperCase();\n\n if (skip(url)) return original.call(this, input, init);\n\n const tx = client.activeTransaction;\n const started = performance.now();\n let span: Span | undefined;\n if (tx?.sampled) span = tx.startChild({ op: 'http.client', description: `${method} ${scrubUrl(url).split(/[?#]/)[0]}`, data: { method } });\n\n const propagate = (client.options.tracesSampleRate ?? 0) > 0 && !!tx && (client.options.tracePropagationTargets === undefined || client.shouldPropagate(url));\n let args: [Parameters<typeof fetch>[0], RequestInit | undefined] = [input, init];\n if (propagate && tx) {\n const headers = new Headers(init?.headers ?? (isRequest ? (input as Request).headers : undefined));\n if (!headers.has(TRACE_HEADER)) headers.set(TRACE_HEADER, formatTraceHeader(tx.traceId, span?.spanId ?? tx.spanId, tx.sampled));\n args = [input, { ...init, headers }];\n }\n\n const finish = (status: number) => {\n if (span) {\n if (status) span.setData('status_code', status);\n span.setStatus(status === 0 ? 'unknown_error' : status >= 500 ? 'internal_error' : 'ok');\n span.end();\n }\n client.addBreadcrumb({\n category: 'http',\n type: 'http',\n level: status >= 500 || status === 0 ? 'error' : status >= 400 ? 'warning' : 'info',\n message: `${method} ${scrubUrl(url)}`,\n data: { method, status_code: status || 'failed', duration_ms: Math.round(performance.now() - started) },\n });\n };\n\n return original.apply(this, args).then(\n (response) => {\n finish(response.status);\n\n return response;\n },\n (error) => {\n finish(0);\n throw error;\n },\n );\n } as typeof fetch;\n\n return () => {\n globalThis.fetch = original;\n };\n },\n };\n}\n","import { gzipSync } from 'node:zlib';\nimport { type Dsn, endpoint, type Payload, type Transport, type TransportResult } from '@greenhat-services/magpie-core';\n\n/** Bodies above this are gzip-compressed, which Magpie accepts. */\nconst COMPRESS_FROM = 4096;\n\n/**\n * Delivers payloads from a server. There is no CORS to avoid here, so the key goes in the `X-Magpie-Auth` header,\n * the same as the Laravel SDK, and the body is JSON, compressed when it is large.\n */\nexport function nodeTransport(dsn: Dsn, fetchImpl: typeof fetch = (...args) => globalThis.fetch(...args), timeoutMs = 3000): Transport {\n return {\n async send(payload: Payload): Promise<TransportResult> {\n try {\n const path = (payload as { type?: string }).type === 'transaction' ? 'transactions' : 'events';\n const json = JSON.stringify(payload);\n const compressed = json.length >= COMPRESS_FROM;\n\n const response = await fetchImpl(endpoint(dsn, path), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Magpie-Auth': `Magpie magpie_version=1, magpie_key=${dsn.publicKey}, magpie_client=magpie-node`,\n ...(compressed ? { 'Content-Encoding': 'gzip' } : {}),\n },\n body: compressed ? new Uint8Array(gzipSync(json)) : json,\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n const retry = Number(response.headers.get('retry-after'));\n\n return { ok: response.ok, status: response.status, retryAfter: Number.isFinite(retry) && retry > 0 ? Math.min(retry, 3600) : undefined };\n } catch {\n // Unreachable, or too slow: monitoring must never hold a request up, so the payload is dropped.\n return { ok: false };\n }\n },\n };\n}\n","import { hostname, platform, release } from 'node:os';\nimport { type Runtime, SDK_VERSION } from '@greenhat-services/magpie-core';\nimport { currentStore } from './context';\nimport { nodeTransport } from './transport';\n\nexport const SDK_NAME = 'magpie.javascript.node';\n\nconst OS_NAMES: Record<string, string> = { win32: 'Windows', darwin: 'macOS', linux: 'Linux', freebsd: 'FreeBSD' };\n\nexport function nodeRuntime(options: { sdkName?: string; fetch?: typeof fetch } = {}): Runtime {\n return {\n sdk: { name: options.sdkName ?? SDK_NAME, version: SDK_VERSION },\n platform: 'node',\n createTransport: (dsn) => nodeTransport(dsn, options.fetch),\n contexts: () => ({\n runtime: { name: 'node', version: process.version.replace(/^v/, '') },\n os: { name: OS_NAMES[platform()] ?? platform(), version: release() },\n }),\n // The request being served, when there is one: set by `trackRequest`, read here for every event.\n request: () => currentStore()?.request,\n };\n}\n\nexport const defaultServerName = (): string | undefined => {\n try {\n return hostname() || undefined;\n } catch {\n return undefined;\n }\n};\n","import { type Client, parseTraceHeader, scrubQuery, TRACE_HEADER, type Transaction } from '@greenhat-services/magpie-core';\nimport { runWithRequest } from './context';\n\nexport interface RequestInfo {\n method: string;\n /** The path (and query string), or a full URL. */\n url: string;\n /** The request's headers, lower-case names. Only a short allow-list of them is ever attached to an event. */\n headers?: Record<string, string | string[] | undefined>;\n /** The matched route pattern (`/users/:id`) when the framework already knows it. */\n route?: string;\n}\n\n/** Headers worth having on an event. Everything else, cookies and authorization above all, is dropped, not filtered. */\nconst SAFE_HEADERS = ['user-agent', 'accept', 'accept-language', 'content-type', 'host', 'referer', 'x-forwarded-proto'];\n\nexport interface RequestHandle {\n transaction: Transaction;\n /** Ends the request's transaction. Pass the route pattern when it is only known now, so `/users/7` becomes `/users/:id`. */\n finish(statusCode: number, route?: string): void;\n}\n\n/**\n * Runs `fn` as one request: its own scope and transaction, the request attached to every event raised inside it, and a\n * trace that continues the caller's `magpie-trace` header (so a page load in the browser and this server render are one\n * trace). Call `finish` when the response is done.\n */\nexport function trackRequest<T>(client: Client, info: RequestInfo, fn: (handle: RequestHandle) => T): T {\n const { path, query } = splitUrl(info.url);\n const request = {\n url: info.headers?.host ? `${firstHeader(info.headers['x-forwarded-proto']) ?? 'http'}://${firstHeader(info.headers.host)}${path}` : path,\n method: info.method.toUpperCase(),\n ...(query ? { query_string: scrubQuery(query) } : {}),\n headers: safeHeaders(info.headers),\n };\n\n return runWithRequest(client, request, () => {\n const parent = parseTraceHeader(firstHeader(info.headers?.[TRACE_HEADER]));\n const method = info.method.toUpperCase();\n const name = (route?: string) => `${method} ${route ?? info.route ?? path}`;\n\n const transaction = client.startTransaction({\n name: name(),\n op: 'http.server',\n ...(parent ? { traceId: parent.traceId, parentSpanId: parent.spanId, sampled: parent.sampled } : {}),\n });\n client.activeTransaction = transaction;\n client.scope.setTransactionName(transaction.name);\n\n const finish = (statusCode: number, route?: string) => {\n transaction.setName(name(route));\n transaction.setData('http.status_code', statusCode);\n // Magpie counts any status but \"ok\" as a failure, so only a server error is one. A 404 is a success.\n if (statusCode >= 500) transaction.setStatus('internal_error');\n transaction.end();\n };\n\n return fn({ transaction, finish });\n });\n}\n\nfunction splitUrl(url: string): { path: string; query: string } {\n let target = url;\n try {\n if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {\n const u = new URL(url);\n target = `${u.pathname}${u.search}`;\n }\n } catch {\n // Keep it as given.\n }\n const q = target.indexOf('?');\n\n return q < 0 ? { path: target, query: '' } : { path: target.slice(0, q), query: target.slice(q + 1) };\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction safeHeaders(headers: RequestInfo['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of SAFE_HEADERS) {\n const value = firstHeader(headers?.[name]);\n if (value) out[name] = value;\n }\n\n return out;\n}\n","import { Client, type Integration, type Options, setClient, getClient } from '@greenhat-services/magpie-core';\nimport { isolatePerRequest } from './context';\nimport { processHandlers } from './handlers';\nimport { nodeHttp } from './http';\nimport { defaultServerName, nodeRuntime } from './runtime';\n\nexport * from '@greenhat-services/magpie-core';\nexport { currentStore, isolatePerRequest, runWithRequest, storage } from './context';\nexport { processHandlers } from './handlers';\nexport { nodeHttp } from './http';\nexport { trackRequest, type RequestHandle, type RequestInfo } from './request';\nexport { nodeTransport } from './transport';\n\nexport interface NodeOptions extends Options {\n /** Report uncaught exceptions and unhandled rejections. Default true. */\n captureUnhandled?: boolean;\n /** Time and propagate the server's own `fetch` calls. Default true. */\n http?: boolean;\n sdkName?: string;\n}\n\n/**\n * Starts Magpie on a server. Per-request data (scope, transaction, the request) is isolated with AsyncLocalStorage, so\n * use `trackRequest` around each request the framework handles.\n */\nexport function init(options: NodeOptions = {}): Client {\n const previous = getClient();\n if (previous) void previous.close(0);\n\n const { captureUnhandled = true, http = true, sdkName, integrations = [], ...rest } = options;\n\n const client = new Client(\n { serverName: defaultServerName(), ...rest, release: rest.release ?? process.env.MAGPIE_RELEASE },\n nodeRuntime({ sdkName }),\n );\n\n isolatePerRequest(client);\n setClient(client);\n\n const defaults: Integration[] = [...(captureUnhandled ? [processHandlers()] : []), ...(http ? [nodeHttp()] : [])];\n client.setupIntegrations([...defaults, ...integrations]);\n\n return client;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
- "name": "@greenhat-services/magpie-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@greenhat-services/magpie-node",
3
+ "version": "0.1.0",
4
+ "description": "Magpie error tracking and performance monitoring for Node.js servers, including Nuxt's Nitro.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "prepack": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "@greenhat-services/magpie-core": "^0.1.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/Greenhat-Services/magpie-js.git",
37
+ "directory": "packages/node"
38
+ },
39
+ "keywords": [
40
+ "magpie",
41
+ "error-tracking",
42
+ "monitoring",
43
+ "node",
44
+ "nitro"
45
+ ]
46
+ }