@nextscope/next 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 Vercel Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # NextScope
2
+
3
+ NextScope is a local OpenTelemetry trace inspector for `next dev`.
4
+
5
+ It is designed for Next.js 16 apps that already use `@vercel/otel >= 2.1`.
6
+ The development plugin starts an in-process OTLP/HTTP JSON trace receiver,
7
+ points `@vercel/otel` at it, and exposes a Next-native inspector at
8
+ `/_nextscope`.
9
+
10
+ The MVP is intentionally narrow:
11
+
12
+ - Next.js 16 only
13
+ - development server only
14
+ - traces only
15
+ - in-memory storage only
16
+ - no external collector, database, or dashboard process
17
+
18
+ ## Why
19
+
20
+ Local OpenTelemetry inspection usually means running something outside the app:
21
+ Jaeger, Grafana LGTM, Aspire Dashboard, or Vercel's collector dev setup. Those
22
+ tools work, but they add a second system to configure and keep in view while
23
+ debugging a Next.js request.
24
+
25
+ NextScope keeps the local debugging loop inside `next dev`. Open your app, make
26
+ a request, then inspect the recent request trace at `/_nextscope`.
27
+
28
+ ## Installation
29
+
30
+ ```sh
31
+ pnpm add @nextscope/next @vercel/otel
32
+ ```
33
+
34
+ Peer requirements:
35
+
36
+ - `next >=16.0.0 <17.0.0`
37
+ - `@vercel/otel >=2.1.0`
38
+
39
+ ## Usage
40
+
41
+ Wrap your Next config with `withNextScope`:
42
+
43
+ ```js
44
+ // next.config.mjs
45
+ import { withNextScope } from '@nextscope/next/config'
46
+
47
+ const nextConfig = {}
48
+
49
+ export default withNextScope(nextConfig)
50
+ ```
51
+
52
+ Register `@vercel/otel` from your app instrumentation file:
53
+
54
+ ```ts
55
+ // instrumentation.ts
56
+ import { registerOTel } from '@vercel/otel'
57
+
58
+ export function register() {
59
+ registerOTel({ serviceName: 'my-next-app' })
60
+ }
61
+ ```
62
+
63
+ Start the app:
64
+
65
+ ```sh
66
+ pnpm dev
67
+ ```
68
+
69
+ Then open:
70
+
71
+ ```text
72
+ http://localhost:3000/_nextscope
73
+ ```
74
+
75
+ ## Verify It Works
76
+
77
+ After setup, keep the app running with `pnpm dev`.
78
+
79
+ 1. Request one page in the app, for example `http://localhost:3000/`.
80
+ 2. Open `http://localhost:3000/_nextscope`.
81
+ 3. Expect at least one trace row in the recent requests list.
82
+
83
+ If no trace row appears, check these common misses:
84
+
85
+ - The app is missing `instrumentation.ts`, or it does not call `registerOTel`
86
+ from `@vercel/otel`.
87
+ - The app is not running through `next dev`. NextScope is disabled for normal
88
+ builds and production starts by default.
89
+ - An existing OTLP exporter uses an unsupported protocol. NextScope captures
90
+ locally, but forwarding only supports OTLP/HTTP JSON.
91
+ - `withNextScope` uses a custom `route`, so the inspector is available at that
92
+ route instead of `/_nextscope`.
93
+ - Another rewrite or route collides with the inspector route.
94
+ - The app has not received any non-NextScope traffic yet. Open a real app page
95
+ before checking the inspector.
96
+
97
+ ## Options
98
+
99
+ ```ts
100
+ withNextScope(nextConfig, {
101
+ enabled: true,
102
+ route: '/_nextscope',
103
+ maxTraces: 200,
104
+ })
105
+ ```
106
+
107
+ | Option | Default | Description |
108
+ | --- | --- | --- |
109
+ | `enabled` | dev auto-detection | Set `false` to disable. Set `true` to force-enable for a non-standard dev runtime. |
110
+ | `route` | `/_nextscope` | Public route where the inspector UI is mounted. |
111
+ | `maxTraces` | `200` | Maximum number of trace groups retained in memory. |
112
+
113
+ By default, NextScope enables itself only for `next dev`. It no-ops for build,
114
+ start, and type generation.
115
+
116
+ ## How It Works
117
+
118
+ During development, the config plugin:
119
+
120
+ 1. Starts a singleton HTTP sidecar on `127.0.0.1:0`.
121
+ 2. Sets `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json`.
122
+ 3. Sets `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` to the sidecar's `/v1/traces`
123
+ endpoint.
124
+ 4. Adds a Next rewrite from `/_nextscope/:path*` to the local sidecar.
125
+
126
+ The sidecar:
127
+
128
+ - accepts `POST /v1/traces` OTLP/HTTP JSON batches;
129
+ - normalizes spans and groups them by `traceId`;
130
+ - keeps recent traces in memory with a retention cap;
131
+ - serves the inspector UI assets;
132
+ - exposes `GET /api/traces` and `GET /api/traces/:traceId` for the UI.
133
+
134
+ The inspector highlights request-oriented traces, especially
135
+ `next.span_type=BaseServer.handleRequest`, and shows:
136
+
137
+ - route or target path;
138
+ - method and status code;
139
+ - duration and start time;
140
+ - span count;
141
+ - nested span waterfall with a tree column and timeline column;
142
+ - service-colored span bars, status and error markers, and event markers;
143
+ - trace-level filtering by text, status, method, newest, slowest, errors, and
144
+ fielded filters such as `route:`, `service:`, `duration:>500ms`, and
145
+ `errors:true`;
146
+ - optional trace list grouping by route with per-route count, latest request,
147
+ slowest request, p50/p95 duration, and error rate;
148
+ - span search across names, service names, span types, status, attributes,
149
+ resource attributes, events, and links;
150
+ - zoomable timing view with `Cmd`/`Ctrl` + mouse wheel, reset zoom, and cursor
151
+ crosshair timing;
152
+ - selected span details with timing, parent/child context, attributes, resource
153
+ attributes, events, and links.
154
+
155
+ The public setup and options are unchanged. The React inspector is bundled into
156
+ static sidecar assets and does not require a separate dashboard process.
157
+
158
+ ## Existing Exporters
159
+
160
+ If an OTLP trace exporter is already configured, NextScope snapshots the previous
161
+ trace endpoint, protocol, and headers before overriding the active trace endpoint.
162
+
163
+ When the previous exporter is compatible with OTLP/HTTP JSON, NextScope forwards
164
+ captured batches to it after local filtering. Unsupported protocols are warned
165
+ once and skipped.
166
+
167
+ Supported forwarding protocol:
168
+
169
+ ```text
170
+ http/json
171
+ ```
172
+
173
+ Unsupported examples include gRPC exporters or non-JSON protocols.
174
+
175
+ ## Self-Trace Suppression
176
+
177
+ Requests made by the inspector itself are filtered out before storage and before
178
+ forwarding. This prevents the tool from filling the trace list with its own UI,
179
+ static asset, or polling requests.
180
+
181
+ Suppressed requests include:
182
+
183
+ - `/_nextscope`
184
+ - `/_nextscope/app.js`
185
+ - `/_nextscope/style.css`
186
+ - `/_nextscope/api/traces`
187
+ - direct sidecar requests for the same UI and API paths
188
+
189
+ ## Fixture App
190
+
191
+ The repository includes a Next.js 16 fixture app at
192
+ `fixtures/next16-app`. It is intentionally heavier than a smoke test so the
193
+ inspector has varied traces to display.
194
+
195
+ Run it with:
196
+
197
+ ```sh
198
+ pnpm --filter nextscope-next16-fixture dev
199
+ ```
200
+
201
+ Useful routes:
202
+
203
+ - `/traced` - minimal traced page
204
+ - `/stress` - fixture dashboard
205
+ - `/stress/nested` - nested spans
206
+ - `/stress/parallel` - parallel spans
207
+ - `/stress/slow` - slow span timing
208
+ - `/stress/error` - recorded exception and error status
209
+ - `/stress/attributes` - large and high-cardinality attributes
210
+ - `/stress/fetch` - fetch-shaped span scenario
211
+ - `/stress/siblings` - many sibling spans
212
+ - `/stress/services` - spans annotated with several service-style peers
213
+ - `/stress/waterfall` - richer sequential and parallel fetch waterfall
214
+ - `/stress/not-found` - traced `notFound()` / 404 route
215
+ - `/stress/long-route-name-with-many-segments/and-a-very-long-leaf` - long route label
216
+ - `/api/stress/attributes` - traced route handler
217
+ - `POST /api/stress/waterfall` - traced POST route handler
218
+ - `/stress/catch/a/b/c` - optional catch-all route
219
+
220
+ Then open:
221
+
222
+ ```text
223
+ http://localhost:3016/_nextscope
224
+ ```
225
+
226
+ ## Development
227
+
228
+ Install dependencies:
229
+
230
+ ```sh
231
+ pnpm install
232
+ ```
233
+
234
+ Run tests:
235
+
236
+ ```sh
237
+ pnpm test
238
+ ```
239
+
240
+ Typecheck:
241
+
242
+ ```sh
243
+ pnpm typecheck
244
+ ```
245
+
246
+ Build the package:
247
+
248
+ ```sh
249
+ pnpm build
250
+ ```
251
+
252
+ Build the fixture app:
253
+
254
+ ```sh
255
+ pnpm --filter nextscope-next16-fixture build
256
+ ```
257
+
258
+ Run the opt-in fixture runtime check:
259
+
260
+ ```sh
261
+ NEXTSCOPE_RUN_RUNTIME=1 pnpm exec vitest run test/fixture-runtime.test.ts
262
+ ```
263
+
264
+ ## Current Limitations
265
+
266
+ - Development-only MVP.
267
+ - Trace data is process-local and lost when the dev server exits.
268
+ - No logs or metrics.
269
+ - No persistence, search index, or cross-session history.
270
+ - Forwarding only supports prior OTLP/HTTP JSON trace exporters.
271
+ - The UI is optimized for recent request debugging, not full observability
272
+ workflows.
@@ -0,0 +1,43 @@
1
+ import type { NextConfig, Rewrites } from './next-types';
2
+ import { type NextScopeServer, type NextScopeServerOptions } from './server';
3
+ export interface NextScopeOptions {
4
+ enabled?: boolean;
5
+ route?: string;
6
+ maxTraces?: number;
7
+ }
8
+ export interface NextScopeRuntime {
9
+ command?: string;
10
+ dev?: boolean;
11
+ }
12
+ export interface NextConfigContext {
13
+ defaultConfig: NextConfig;
14
+ command?: string;
15
+ dev?: boolean;
16
+ }
17
+ export type NextConfigFactory = (phase: string, context?: NextConfigContext) => Promise<NextConfig>;
18
+ export type NextConfigInputFactory = (phase: string, context: NextConfigContext) => NextConfig | Promise<NextConfig>;
19
+ export declare function withNextScope(nextConfig?: NextConfig, options?: NextScopeOptions): NextConfigFactory;
20
+ export declare function withNextScope(nextConfig: NextConfigInputFactory, options?: NextScopeOptions): NextConfigFactory;
21
+ export declare function resetNextScopeForTests(): void;
22
+ export declare function shouldEnable(runtime: NextScopeRuntime, options?: NextScopeOptions): boolean;
23
+ export declare function normalizeRoute(route: string): string;
24
+ export declare function resolveRewrites(rewrites: NextConfig['rewrites']): Promise<Rewrites>;
25
+ export declare function mergeRewrites(existing: Rewrites | undefined, nextScopeRewrite: Rewrite): Rewrite[] | {
26
+ beforeFiles: Rewrite[];
27
+ afterFiles?: import("./next-types").Rewrite[];
28
+ fallback?: import("./next-types").Rewrite[];
29
+ };
30
+ export declare function ensureServer(options: NextScopeServerOptions): Promise<NextScopeServer | undefined>;
31
+ export declare function configureTelemetryEnv(nextScopeEndpoint: string): void;
32
+ export declare function snapshotTraceExporterEnv(): {
33
+ endpoint: string | undefined;
34
+ protocol: string | undefined;
35
+ headers: string | undefined;
36
+ };
37
+ export declare function isSupportedHttpJson(protocol?: string): boolean;
38
+ interface Rewrite {
39
+ source: string;
40
+ destination: string;
41
+ basePath?: false;
42
+ }
43
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withNextScope = withNextScope;
4
+ exports.resetNextScopeForTests = resetNextScopeForTests;
5
+ exports.shouldEnable = shouldEnable;
6
+ exports.normalizeRoute = normalizeRoute;
7
+ exports.resolveRewrites = resolveRewrites;
8
+ exports.mergeRewrites = mergeRewrites;
9
+ exports.ensureServer = ensureServer;
10
+ exports.configureTelemetryEnv = configureTelemetryEnv;
11
+ exports.snapshotTraceExporterEnv = snapshotTraceExporterEnv;
12
+ exports.isSupportedHttpJson = isSupportedHttpJson;
13
+ const server_1 = require("./server");
14
+ let singletonServer;
15
+ let warnedUnsupportedExporter = false;
16
+ let warnedStartupFailure = false;
17
+ const DEFAULT_ROUTE = '/_nextscope';
18
+ const DEFAULT_MAX_TRACES = 200;
19
+ function withNextScope(nextConfig = {}, options = {}) {
20
+ const wrapped = async (phase, context = { defaultConfig: {} }) => {
21
+ const resolvedConfig = typeof nextConfig === 'function'
22
+ ? await nextConfig(phase, context)
23
+ : nextConfig;
24
+ const runtime = inferRuntime(phase, context);
25
+ if (!shouldEnable(runtime, options)) {
26
+ return resolvedConfig;
27
+ }
28
+ const server = await ensureServer({
29
+ route: normalizeRoute(options.route ?? DEFAULT_ROUTE),
30
+ maxTraces: options.maxTraces ?? DEFAULT_MAX_TRACES,
31
+ });
32
+ if (!server) {
33
+ return resolvedConfig;
34
+ }
35
+ configureTelemetryEnv(server.url);
36
+ return {
37
+ ...resolvedConfig,
38
+ rewrites: async () => {
39
+ const existing = await resolveRewrites(resolvedConfig.rewrites);
40
+ return mergeRewrites(existing, {
41
+ source: `${server.route}/:path*`,
42
+ destination: `${server.url}/:path*`,
43
+ basePath: false,
44
+ });
45
+ },
46
+ };
47
+ };
48
+ return wrapped;
49
+ }
50
+ function resetNextScopeForTests() {
51
+ singletonServer = undefined;
52
+ warnedUnsupportedExporter = false;
53
+ warnedStartupFailure = false;
54
+ }
55
+ function shouldEnable(runtime, options = {}) {
56
+ if (options.enabled === false) {
57
+ return false;
58
+ }
59
+ if (options.enabled === true) {
60
+ return true;
61
+ }
62
+ return runtime.dev === true || runtime.command === 'dev';
63
+ }
64
+ function normalizeRoute(route) {
65
+ const trimmed = route.trim();
66
+ const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
67
+ return withSlash.length > 1 ? withSlash.replace(/\/+$/, '') : withSlash;
68
+ }
69
+ async function resolveRewrites(rewrites) {
70
+ if (!rewrites) {
71
+ return [];
72
+ }
73
+ return typeof rewrites === 'function' ? await rewrites() : rewrites;
74
+ }
75
+ function mergeRewrites(existing, nextScopeRewrite) {
76
+ if (!existing) {
77
+ return [nextScopeRewrite];
78
+ }
79
+ if (Array.isArray(existing)) {
80
+ return [nextScopeRewrite, ...existing];
81
+ }
82
+ return {
83
+ ...existing,
84
+ beforeFiles: [nextScopeRewrite, ...(existing.beforeFiles ?? [])],
85
+ };
86
+ }
87
+ async function ensureServer(options) {
88
+ if (singletonServer) {
89
+ return singletonServer;
90
+ }
91
+ try {
92
+ singletonServer = await (0, server_1.startNextScopeServer)(options);
93
+ return singletonServer;
94
+ }
95
+ catch (error) {
96
+ if (!warnedStartupFailure) {
97
+ warnedStartupFailure = true;
98
+ console.warn(`[nextscope] Failed to start local trace inspector: ${formatError(error)}`);
99
+ }
100
+ return undefined;
101
+ }
102
+ }
103
+ function configureTelemetryEnv(nextScopeEndpoint) {
104
+ const nextScopeTraceEndpoint = `${nextScopeEndpoint}/v1/traces`;
105
+ const previous = snapshotTraceExporterEnv();
106
+ if (previous.endpoint !== nextScopeTraceEndpoint) {
107
+ process.env.NEXTSCOPE_FORWARD_TRACES_ENDPOINT = previous.endpoint ?? '';
108
+ process.env.NEXTSCOPE_FORWARD_TRACES_PROTOCOL = previous.protocol ?? '';
109
+ process.env.NEXTSCOPE_FORWARD_TRACES_HEADERS = previous.headers ?? '';
110
+ }
111
+ if (previous.endpoint && !isSupportedHttpJson(previous.protocol)) {
112
+ warnUnsupportedExporter(previous.protocol);
113
+ }
114
+ process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'http/json';
115
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = nextScopeTraceEndpoint;
116
+ }
117
+ function snapshotTraceExporterEnv() {
118
+ return {
119
+ endpoint: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ??
120
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
121
+ protocol: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ??
122
+ process.env.OTEL_EXPORTER_OTLP_PROTOCOL,
123
+ headers: process.env.OTEL_EXPORTER_OTLP_TRACES_HEADERS ??
124
+ process.env.OTEL_EXPORTER_OTLP_HEADERS,
125
+ };
126
+ }
127
+ function isSupportedHttpJson(protocol) {
128
+ return !protocol || protocol === 'http/json';
129
+ }
130
+ function inferRuntime(phase, context) {
131
+ return {
132
+ command: typeof context.defaultConfig === 'object' &&
133
+ context.defaultConfig !== null &&
134
+ 'command' in context.defaultConfig &&
135
+ typeof context.defaultConfig.command === 'string'
136
+ ? context.defaultConfig.command
137
+ : typeof context.command === 'string'
138
+ ? context.command
139
+ : undefined,
140
+ dev: typeof context.dev === 'boolean'
141
+ ? context.dev
142
+ : phase === 'phase-development-server',
143
+ };
144
+ }
145
+ function warnUnsupportedExporter(protocol) {
146
+ if (warnedUnsupportedExporter) {
147
+ return;
148
+ }
149
+ warnedUnsupportedExporter = true;
150
+ console.warn(`[nextscope] Existing OTLP trace exporter uses unsupported protocol "${protocol ?? 'unset'}"; capturing locally but forwarding is disabled.`);
151
+ }
152
+ function formatError(error) {
153
+ return error instanceof Error ? error.message : String(error);
154
+ }
155
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface ForwarderEnv {
2
+ endpoint?: string;
3
+ protocol?: string;
4
+ headers?: string;
5
+ }
6
+ export interface ForwarderStatus {
7
+ configured: boolean;
8
+ enabled: boolean;
9
+ protocol?: string;
10
+ endpoint?: string;
11
+ lastSuccessAt?: string;
12
+ lastError?: string;
13
+ }
14
+ export declare class TraceForwarder {
15
+ private readonly env;
16
+ private warned;
17
+ private lastSuccessAt;
18
+ private lastError;
19
+ constructor(env?: ForwarderEnv);
20
+ forward(body: string): Promise<void>;
21
+ getStatus(): ForwarderStatus;
22
+ private warnOnce;
23
+ }
24
+ export declare function readForwarderEnv(): ForwarderEnv;
25
+ export declare function isForwardableProtocol(protocol?: string): boolean;
26
+ export declare function parseHeaders(headerString?: string): Record<string, string>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TraceForwarder = void 0;
4
+ exports.readForwarderEnv = readForwarderEnv;
5
+ exports.isForwardableProtocol = isForwardableProtocol;
6
+ exports.parseHeaders = parseHeaders;
7
+ class TraceForwarder {
8
+ env;
9
+ warned = false;
10
+ lastSuccessAt;
11
+ lastError;
12
+ constructor(env = readForwarderEnv()) {
13
+ this.env = env;
14
+ }
15
+ async forward(body) {
16
+ if (!this.env.endpoint || !isForwardableProtocol(this.env.protocol)) {
17
+ return;
18
+ }
19
+ try {
20
+ const response = await fetch(this.env.endpoint, {
21
+ method: 'POST',
22
+ body,
23
+ headers: {
24
+ 'content-type': 'application/json',
25
+ ...parseHeaders(this.env.headers),
26
+ },
27
+ });
28
+ if (!response.ok) {
29
+ throw new Error(`forwarding failed with HTTP ${response.status}`);
30
+ }
31
+ this.lastSuccessAt = Date.now();
32
+ this.lastError = undefined;
33
+ }
34
+ catch (error) {
35
+ this.lastError = formatError(error);
36
+ this.warnOnce(error);
37
+ }
38
+ }
39
+ getStatus() {
40
+ return {
41
+ configured: Boolean(this.env.endpoint),
42
+ enabled: Boolean(this.env.endpoint && isForwardableProtocol(this.env.protocol)),
43
+ protocol: this.env.protocol,
44
+ endpoint: this.env.endpoint,
45
+ lastSuccessAt: this.lastSuccessAt
46
+ ? new Date(this.lastSuccessAt).toISOString()
47
+ : undefined,
48
+ lastError: this.lastError,
49
+ };
50
+ }
51
+ warnOnce(error) {
52
+ if (this.warned) {
53
+ return;
54
+ }
55
+ this.warned = true;
56
+ console.warn(`[nextscope] Failed to forward trace batch to existing exporter: ${formatError(error)}`);
57
+ }
58
+ }
59
+ exports.TraceForwarder = TraceForwarder;
60
+ function readForwarderEnv() {
61
+ return {
62
+ endpoint: process.env.NEXTSCOPE_FORWARD_TRACES_ENDPOINT || undefined,
63
+ protocol: process.env.NEXTSCOPE_FORWARD_TRACES_PROTOCOL || undefined,
64
+ headers: process.env.NEXTSCOPE_FORWARD_TRACES_HEADERS || undefined,
65
+ };
66
+ }
67
+ function isForwardableProtocol(protocol) {
68
+ return !protocol || protocol === 'http/json';
69
+ }
70
+ function parseHeaders(headerString) {
71
+ const headers = {};
72
+ if (!headerString) {
73
+ return headers;
74
+ }
75
+ for (const part of headerString.split(',')) {
76
+ const [rawKey, ...rawValue] = part.split('=');
77
+ const key = rawKey?.trim();
78
+ const value = rawValue.join('=').trim();
79
+ if (key && value) {
80
+ headers[key] = value;
81
+ }
82
+ }
83
+ return headers;
84
+ }
85
+ function formatError(error) {
86
+ return error instanceof Error ? error.message : String(error);
87
+ }
88
+ //# sourceMappingURL=forwarder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.js","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"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"}