@effect-gql/opentelemetry 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,7 @@
1
+ Copyright 2025 Nick Fisher
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ import { Effect, Context } from "effect";
2
+ import { HttpServerRequest } from "@effect/platform";
3
+ /**
4
+ * W3C Trace Context header names
5
+ * @see https://www.w3.org/TR/trace-context/
6
+ */
7
+ export declare const TRACEPARENT_HEADER = "traceparent";
8
+ export declare const TRACESTATE_HEADER = "tracestate";
9
+ /**
10
+ * Parsed W3C Trace Context from incoming HTTP headers
11
+ */
12
+ export interface TraceContext {
13
+ /**
14
+ * Version of the trace context format (always "00" for current spec)
15
+ */
16
+ readonly version: string;
17
+ /**
18
+ * Trace ID - 32 character lowercase hex string
19
+ */
20
+ readonly traceId: string;
21
+ /**
22
+ * Parent Span ID - 16 character lowercase hex string
23
+ */
24
+ readonly parentSpanId: string;
25
+ /**
26
+ * Trace flags - determines if trace is sampled
27
+ * Bit 0 (0x01) = sampled flag
28
+ */
29
+ readonly traceFlags: number;
30
+ /**
31
+ * Optional trace state from upstream services
32
+ */
33
+ readonly traceState?: string;
34
+ }
35
+ declare const TraceContextTag_base: Context.TagClass<TraceContextTag, "@effect-gql/opentelemetry/TraceContext", TraceContext>;
36
+ /**
37
+ * Context tag for TraceContext
38
+ */
39
+ export declare class TraceContextTag extends TraceContextTag_base {
40
+ }
41
+ /**
42
+ * Parse a W3C traceparent header value.
43
+ *
44
+ * Format: {version}-{trace-id}-{parent-id}-{trace-flags}
45
+ * Example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
46
+ *
47
+ * @see https://www.w3.org/TR/trace-context/#traceparent-header
48
+ *
49
+ * @param header - The traceparent header value
50
+ * @returns Parsed trace context or null if invalid
51
+ */
52
+ export declare const parseTraceParent: (header: string) => TraceContext | null;
53
+ /**
54
+ * Check if a trace context is sampled (should be recorded)
55
+ */
56
+ export declare const isSampled: (context: TraceContext) => boolean;
57
+ /**
58
+ * Extract trace context from HTTP request headers.
59
+ *
60
+ * Looks for the W3C Trace Context headers:
61
+ * - `traceparent`: Required, contains trace ID, span ID, and flags
62
+ * - `tracestate`: Optional, vendor-specific trace data
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const context = yield* extractTraceContext
67
+ * if (context) {
68
+ * console.log(`Continuing trace: ${context.traceId}`)
69
+ * }
70
+ * ```
71
+ */
72
+ export declare const extractTraceContext: Effect.Effect<TraceContext | null, never, HttpServerRequest.HttpServerRequest>;
73
+ /**
74
+ * Format a trace context as a traceparent header value
75
+ */
76
+ export declare const formatTraceParent: (context: TraceContext) => string;
77
+ export {};
78
+ //# sourceMappingURL=context-propagation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-propagation.d.ts","sourceRoot":"","sources":["../src/context-propagation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEpD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,gBAAgB,CAAA;AAC/C,eAAO,MAAM,iBAAiB,eAAe,CAAA;AAE7C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAE7B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,oBAGlC;CAAG;AAEN;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,KAAG,YAAY,GAAG,IA4ChE,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,YAAY,KAAG,OAEjD,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAC7C,YAAY,GAAG,IAAI,EACnB,KAAK,EACL,iBAAiB,CAAC,iBAAiB,CAqCnC,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,YAAY,KAAG,MAGzD,CAAA"}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTraceParent = exports.extractTraceContext = exports.isSampled = exports.parseTraceParent = exports.TraceContextTag = exports.TRACESTATE_HEADER = exports.TRACEPARENT_HEADER = void 0;
4
+ const effect_1 = require("effect");
5
+ const platform_1 = require("@effect/platform");
6
+ /**
7
+ * W3C Trace Context header names
8
+ * @see https://www.w3.org/TR/trace-context/
9
+ */
10
+ exports.TRACEPARENT_HEADER = "traceparent";
11
+ exports.TRACESTATE_HEADER = "tracestate";
12
+ /**
13
+ * Context tag for TraceContext
14
+ */
15
+ class TraceContextTag extends effect_1.Context.Tag("@effect-gql/opentelemetry/TraceContext")() {
16
+ }
17
+ exports.TraceContextTag = TraceContextTag;
18
+ /**
19
+ * Parse a W3C traceparent header value.
20
+ *
21
+ * Format: {version}-{trace-id}-{parent-id}-{trace-flags}
22
+ * Example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
23
+ *
24
+ * @see https://www.w3.org/TR/trace-context/#traceparent-header
25
+ *
26
+ * @param header - The traceparent header value
27
+ * @returns Parsed trace context or null if invalid
28
+ */
29
+ const parseTraceParent = (header) => {
30
+ const trimmed = header.trim().toLowerCase();
31
+ const parts = trimmed.split("-");
32
+ if (parts.length !== 4) {
33
+ return null;
34
+ }
35
+ const [version, traceId, parentSpanId, flagsHex] = parts;
36
+ // Validate version (2 hex chars)
37
+ if (version.length !== 2 || !/^[0-9a-f]{2}$/.test(version)) {
38
+ return null;
39
+ }
40
+ // Validate trace ID (32 hex chars, not all zeros)
41
+ if (traceId.length !== 32 || !/^[0-9a-f]{32}$/.test(traceId)) {
42
+ return null;
43
+ }
44
+ if (traceId === "00000000000000000000000000000000") {
45
+ return null;
46
+ }
47
+ // Validate parent span ID (16 hex chars, not all zeros)
48
+ if (parentSpanId.length !== 16 || !/^[0-9a-f]{16}$/.test(parentSpanId)) {
49
+ return null;
50
+ }
51
+ if (parentSpanId === "0000000000000000") {
52
+ return null;
53
+ }
54
+ // Validate trace flags (2 hex chars)
55
+ if (flagsHex.length !== 2 || !/^[0-9a-f]{2}$/.test(flagsHex)) {
56
+ return null;
57
+ }
58
+ const traceFlags = parseInt(flagsHex, 16);
59
+ return {
60
+ version,
61
+ traceId,
62
+ parentSpanId,
63
+ traceFlags,
64
+ };
65
+ };
66
+ exports.parseTraceParent = parseTraceParent;
67
+ /**
68
+ * Check if a trace context is sampled (should be recorded)
69
+ */
70
+ const isSampled = (context) => {
71
+ return (context.traceFlags & 0x01) === 0x01;
72
+ };
73
+ exports.isSampled = isSampled;
74
+ /**
75
+ * Extract trace context from HTTP request headers.
76
+ *
77
+ * Looks for the W3C Trace Context headers:
78
+ * - `traceparent`: Required, contains trace ID, span ID, and flags
79
+ * - `tracestate`: Optional, vendor-specific trace data
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const context = yield* extractTraceContext
84
+ * if (context) {
85
+ * console.log(`Continuing trace: ${context.traceId}`)
86
+ * }
87
+ * ```
88
+ */
89
+ exports.extractTraceContext = effect_1.Effect.gen(function* () {
90
+ const request = yield* platform_1.HttpServerRequest.HttpServerRequest;
91
+ const headers = request.headers;
92
+ // Get traceparent header (case-insensitive)
93
+ const traceparentKey = Object.keys(headers).find((k) => k.toLowerCase() === exports.TRACEPARENT_HEADER);
94
+ if (!traceparentKey) {
95
+ return null;
96
+ }
97
+ const traceparentValue = headers[traceparentKey];
98
+ const traceparent = Array.isArray(traceparentValue) ? traceparentValue[0] : traceparentValue;
99
+ if (!traceparent) {
100
+ return null;
101
+ }
102
+ const context = (0, exports.parseTraceParent)(traceparent);
103
+ if (!context) {
104
+ return null;
105
+ }
106
+ // Get optional tracestate header
107
+ const tracestateKey = Object.keys(headers).find((k) => k.toLowerCase() === exports.TRACESTATE_HEADER);
108
+ if (tracestateKey) {
109
+ const tracestateValue = headers[tracestateKey];
110
+ const tracestate = Array.isArray(tracestateValue) ? tracestateValue[0] : tracestateValue;
111
+ if (tracestate) {
112
+ return { ...context, traceState: tracestate };
113
+ }
114
+ }
115
+ return context;
116
+ });
117
+ /**
118
+ * Format a trace context as a traceparent header value
119
+ */
120
+ const formatTraceParent = (context) => {
121
+ const flags = context.traceFlags.toString(16).padStart(2, "0");
122
+ return `${context.version}-${context.traceId}-${context.parentSpanId}-${flags}`;
123
+ };
124
+ exports.formatTraceParent = formatTraceParent;
125
+ //# sourceMappingURL=context-propagation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-propagation.js","sourceRoot":"","sources":["../src/context-propagation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @effect-gql/opentelemetry
3
+ *
4
+ * OpenTelemetry tracing integration for Effect GraphQL.
5
+ *
6
+ * Provides distributed tracing using Effect's native OpenTelemetry support.
7
+ * Works with any OpenTelemetry-compatible backend (Jaeger, Tempo, Honeycomb, etc.).
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { GraphQLSchemaBuilder } from "@effect-gql/core"
12
+ * import { serve } from "@effect-gql/node"
13
+ * import { withTracing } from "@effect-gql/opentelemetry"
14
+ * import { NodeSdk } from "@effect/opentelemetry"
15
+ * import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"
16
+ * import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
17
+ *
18
+ * // Add tracing to schema
19
+ * const builder = GraphQLSchemaBuilder.empty
20
+ * .query("users", { ... })
21
+ * .pipe(withTracing({
22
+ * extension: { exposeTraceIdInResponse: true },
23
+ * resolver: { excludePatterns: [/^Query\.__/] }
24
+ * }))
25
+ *
26
+ * // Configure OpenTelemetry
27
+ * const TracingLayer = NodeSdk.layer(() => ({
28
+ * resource: { serviceName: "my-graphql-api" },
29
+ * spanProcessor: new BatchSpanProcessor(
30
+ * new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" })
31
+ * )
32
+ * }))
33
+ *
34
+ * // Serve with tracing
35
+ * serve(builder, TracingLayer.pipe(Layer.merge(serviceLayer)))
36
+ * ```
37
+ *
38
+ * @packageDocumentation
39
+ */
40
+ import type { GraphQLSchemaBuilder } from "@effect-gql/core";
41
+ import { type TracingExtensionConfig } from "./tracing-extension";
42
+ import { type ResolverTracingConfig } from "./tracing-middleware";
43
+ /**
44
+ * Complete configuration for GraphQL tracing
45
+ */
46
+ export interface GraphQLTracingConfig {
47
+ /**
48
+ * Configuration for phase-level tracing (parse, validate, execute).
49
+ * Uses the Extensions system.
50
+ */
51
+ readonly extension?: TracingExtensionConfig;
52
+ /**
53
+ * Configuration for resolver-level tracing.
54
+ * Uses the Middleware system.
55
+ */
56
+ readonly resolver?: ResolverTracingConfig;
57
+ }
58
+ /**
59
+ * Add OpenTelemetry tracing to a GraphQL schema builder.
60
+ *
61
+ * This is a convenience function that registers both the tracing extension
62
+ * (for phase-level spans) and resolver middleware (for field-level spans).
63
+ *
64
+ * **Span Hierarchy:**
65
+ * ```
66
+ * graphql.request (if using traced router)
67
+ * ├── graphql.parse
68
+ * ├── graphql.validate
69
+ * └── graphql.execute
70
+ * ├── graphql.resolve Query.users
71
+ * ├── graphql.resolve User.posts
72
+ * └── graphql.resolve Post.author
73
+ * ```
74
+ *
75
+ * **Requirements:**
76
+ * - An OpenTelemetry tracer must be provided via Effect's tracing layer
77
+ * - Use `@effect/opentelemetry` NodeSdk.layer or OtlpTracer.layer
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * import { GraphQLSchemaBuilder } from "@effect-gql/core"
82
+ * import { withTracing } from "@effect-gql/opentelemetry"
83
+ *
84
+ * const builder = GraphQLSchemaBuilder.empty
85
+ * .query("hello", {
86
+ * type: S.String,
87
+ * resolve: () => Effect.succeed("world")
88
+ * })
89
+ * .pipe(withTracing({
90
+ * extension: {
91
+ * exposeTraceIdInResponse: true, // Add traceId to response extensions
92
+ * includeQuery: false, // Don't include query in spans (security)
93
+ * },
94
+ * resolver: {
95
+ * minDepth: 0, // Trace all resolvers
96
+ * excludePatterns: [/^Query\.__/], // Skip introspection
97
+ * includeArgs: false, // Don't include args (security)
98
+ * }
99
+ * }))
100
+ * ```
101
+ *
102
+ * @param config - Optional tracing configuration
103
+ * @returns A function that adds tracing to a GraphQLSchemaBuilder
104
+ */
105
+ export declare const withTracing: <R>(config?: GraphQLTracingConfig) => (builder: GraphQLSchemaBuilder<R>) => GraphQLSchemaBuilder<R>;
106
+ export { tracingExtension, type TracingExtensionConfig } from "./tracing-extension";
107
+ export { resolverTracingMiddleware, type ResolverTracingConfig } from "./tracing-middleware";
108
+ export { extractTraceContext, parseTraceParent, formatTraceParent, isSampled, TraceContextTag, TRACEPARENT_HEADER, TRACESTATE_HEADER, type TraceContext, } from "./context-propagation";
109
+ export { pathToString, getFieldDepth, isIntrospectionField } from "./utils";
110
+ export { makeTracedGraphQLRouter, withTracedRouter, type TracedRouterOptions, } from "./traced-router";
111
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACnF,OAAO,EAA6B,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE5F;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAA;IAE3C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,qBAAqB,CAAA;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,WAAW,GACrB,CAAC,EAAE,SAAS,oBAAoB,MAChC,SAAS,oBAAoB,CAAC,CAAC,CAAC,KAAG,oBAAoB,CAAC,CAAC,CAQzD,CAAA;AAGH,OAAO,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACnF,OAAO,EAAE,yBAAyB,EAAE,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5F,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC3E,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * @effect-gql/opentelemetry
4
+ *
5
+ * OpenTelemetry tracing integration for Effect GraphQL.
6
+ *
7
+ * Provides distributed tracing using Effect's native OpenTelemetry support.
8
+ * Works with any OpenTelemetry-compatible backend (Jaeger, Tempo, Honeycomb, etc.).
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { GraphQLSchemaBuilder } from "@effect-gql/core"
13
+ * import { serve } from "@effect-gql/node"
14
+ * import { withTracing } from "@effect-gql/opentelemetry"
15
+ * import { NodeSdk } from "@effect/opentelemetry"
16
+ * import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"
17
+ * import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
18
+ *
19
+ * // Add tracing to schema
20
+ * const builder = GraphQLSchemaBuilder.empty
21
+ * .query("users", { ... })
22
+ * .pipe(withTracing({
23
+ * extension: { exposeTraceIdInResponse: true },
24
+ * resolver: { excludePatterns: [/^Query\.__/] }
25
+ * }))
26
+ *
27
+ * // Configure OpenTelemetry
28
+ * const TracingLayer = NodeSdk.layer(() => ({
29
+ * resource: { serviceName: "my-graphql-api" },
30
+ * spanProcessor: new BatchSpanProcessor(
31
+ * new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" })
32
+ * )
33
+ * }))
34
+ *
35
+ * // Serve with tracing
36
+ * serve(builder, TracingLayer.pipe(Layer.merge(serviceLayer)))
37
+ * ```
38
+ *
39
+ * @packageDocumentation
40
+ */
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.withTracedRouter = exports.makeTracedGraphQLRouter = exports.isIntrospectionField = exports.getFieldDepth = exports.pathToString = exports.TRACESTATE_HEADER = exports.TRACEPARENT_HEADER = exports.TraceContextTag = exports.isSampled = exports.formatTraceParent = exports.parseTraceParent = exports.extractTraceContext = exports.resolverTracingMiddleware = exports.tracingExtension = exports.withTracing = void 0;
43
+ const tracing_extension_1 = require("./tracing-extension");
44
+ const tracing_middleware_1 = require("./tracing-middleware");
45
+ /**
46
+ * Add OpenTelemetry tracing to a GraphQL schema builder.
47
+ *
48
+ * This is a convenience function that registers both the tracing extension
49
+ * (for phase-level spans) and resolver middleware (for field-level spans).
50
+ *
51
+ * **Span Hierarchy:**
52
+ * ```
53
+ * graphql.request (if using traced router)
54
+ * ├── graphql.parse
55
+ * ├── graphql.validate
56
+ * └── graphql.execute
57
+ * ├── graphql.resolve Query.users
58
+ * ├── graphql.resolve User.posts
59
+ * └── graphql.resolve Post.author
60
+ * ```
61
+ *
62
+ * **Requirements:**
63
+ * - An OpenTelemetry tracer must be provided via Effect's tracing layer
64
+ * - Use `@effect/opentelemetry` NodeSdk.layer or OtlpTracer.layer
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * import { GraphQLSchemaBuilder } from "@effect-gql/core"
69
+ * import { withTracing } from "@effect-gql/opentelemetry"
70
+ *
71
+ * const builder = GraphQLSchemaBuilder.empty
72
+ * .query("hello", {
73
+ * type: S.String,
74
+ * resolve: () => Effect.succeed("world")
75
+ * })
76
+ * .pipe(withTracing({
77
+ * extension: {
78
+ * exposeTraceIdInResponse: true, // Add traceId to response extensions
79
+ * includeQuery: false, // Don't include query in spans (security)
80
+ * },
81
+ * resolver: {
82
+ * minDepth: 0, // Trace all resolvers
83
+ * excludePatterns: [/^Query\.__/], // Skip introspection
84
+ * includeArgs: false, // Don't include args (security)
85
+ * }
86
+ * }))
87
+ * ```
88
+ *
89
+ * @param config - Optional tracing configuration
90
+ * @returns A function that adds tracing to a GraphQLSchemaBuilder
91
+ */
92
+ const withTracing = (config) => (builder) => {
93
+ // Add tracing extension for phase-level spans
94
+ let result = builder.extension((0, tracing_extension_1.tracingExtension)(config?.extension));
95
+ // Add resolver tracing middleware for field-level spans
96
+ result = result.middleware((0, tracing_middleware_1.resolverTracingMiddleware)(config?.resolver));
97
+ return result;
98
+ };
99
+ exports.withTracing = withTracing;
100
+ // Re-export components for individual use
101
+ var tracing_extension_2 = require("./tracing-extension");
102
+ Object.defineProperty(exports, "tracingExtension", { enumerable: true, get: function () { return tracing_extension_2.tracingExtension; } });
103
+ var tracing_middleware_2 = require("./tracing-middleware");
104
+ Object.defineProperty(exports, "resolverTracingMiddleware", { enumerable: true, get: function () { return tracing_middleware_2.resolverTracingMiddleware; } });
105
+ var context_propagation_1 = require("./context-propagation");
106
+ Object.defineProperty(exports, "extractTraceContext", { enumerable: true, get: function () { return context_propagation_1.extractTraceContext; } });
107
+ Object.defineProperty(exports, "parseTraceParent", { enumerable: true, get: function () { return context_propagation_1.parseTraceParent; } });
108
+ Object.defineProperty(exports, "formatTraceParent", { enumerable: true, get: function () { return context_propagation_1.formatTraceParent; } });
109
+ Object.defineProperty(exports, "isSampled", { enumerable: true, get: function () { return context_propagation_1.isSampled; } });
110
+ Object.defineProperty(exports, "TraceContextTag", { enumerable: true, get: function () { return context_propagation_1.TraceContextTag; } });
111
+ Object.defineProperty(exports, "TRACEPARENT_HEADER", { enumerable: true, get: function () { return context_propagation_1.TRACEPARENT_HEADER; } });
112
+ Object.defineProperty(exports, "TRACESTATE_HEADER", { enumerable: true, get: function () { return context_propagation_1.TRACESTATE_HEADER; } });
113
+ var utils_1 = require("./utils");
114
+ Object.defineProperty(exports, "pathToString", { enumerable: true, get: function () { return utils_1.pathToString; } });
115
+ Object.defineProperty(exports, "getFieldDepth", { enumerable: true, get: function () { return utils_1.getFieldDepth; } });
116
+ Object.defineProperty(exports, "isIntrospectionField", { enumerable: true, get: function () { return utils_1.isIntrospectionField; } });
117
+ var traced_router_1 = require("./traced-router");
118
+ Object.defineProperty(exports, "makeTracedGraphQLRouter", { enumerable: true, get: function () { return traced_router_1.makeTracedGraphQLRouter; } });
119
+ Object.defineProperty(exports, "withTracedRouter", { enumerable: true, get: function () { return traced_router_1.withTracedRouter; } });
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;;;AAGH,2DAAmF;AACnF,6DAA4F;AAmB5F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACI,MAAM,WAAW,GACtB,CAAI,MAA6B,EAAE,EAAE,CACrC,CAAC,OAAgC,EAA2B,EAAE;IAC5D,8CAA8C;IAC9C,IAAI,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,IAAA,oCAAgB,EAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;IAEnE,wDAAwD;IACxD,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,IAAA,8CAAyB,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEvE,OAAO,MAAiC,CAAA;AAC1C,CAAC,CAAA;AAVU,QAAA,WAAW,eAUrB;AAEH,0CAA0C;AAC1C,yDAAmF;AAA1E,qHAAA,gBAAgB,OAAA;AACzB,2DAA4F;AAAnF,+HAAA,yBAAyB,OAAA;AAClC,6DAS8B;AAR5B,0HAAA,mBAAmB,OAAA;AACnB,uHAAA,gBAAgB,OAAA;AAChB,wHAAA,iBAAiB,OAAA;AACjB,gHAAA,SAAS,OAAA;AACT,sHAAA,eAAe,OAAA;AACf,yHAAA,kBAAkB,OAAA;AAClB,wHAAA,iBAAiB,OAAA;AAGnB,iCAA2E;AAAlE,qGAAA,YAAY,OAAA;AAAE,sGAAA,aAAa,OAAA;AAAE,6GAAA,oBAAoB,OAAA;AAC1D,iDAIwB;AAHtB,wHAAA,uBAAuB,OAAA;AACvB,iHAAA,gBAAgB,OAAA"}
@@ -0,0 +1,90 @@
1
+ import { HttpRouter } from "@effect/platform";
2
+ import { Layer } from "effect";
3
+ import type { GraphQLSchema } from "graphql";
4
+ import { type MakeGraphQLRouterOptions } from "@effect-gql/core/server";
5
+ /**
6
+ * Options for the traced GraphQL router
7
+ */
8
+ export interface TracedRouterOptions extends MakeGraphQLRouterOptions {
9
+ /**
10
+ * Name for the root HTTP span.
11
+ * Default: "graphql.http"
12
+ */
13
+ readonly rootSpanName?: string;
14
+ /**
15
+ * Additional attributes to add to the root span.
16
+ */
17
+ readonly rootSpanAttributes?: Record<string, string | number | boolean>;
18
+ /**
19
+ * Whether to propagate trace context from incoming HTTP headers.
20
+ * Uses W3C Trace Context (traceparent header).
21
+ * Default: true
22
+ */
23
+ readonly propagateContext?: boolean;
24
+ }
25
+ /**
26
+ * Creates a GraphQL router with OpenTelemetry tracing at the HTTP level.
27
+ *
28
+ * This wraps the standard makeGraphQLRouter to:
29
+ * 1. Extract trace context from incoming HTTP headers (W3C Trace Context)
30
+ * 2. Create a root span for the entire HTTP request
31
+ * 3. Propagate trace context to child spans created by extensions/middleware
32
+ *
33
+ * **Span Hierarchy:**
34
+ * ```
35
+ * graphql.http (created by this router)
36
+ * ├── graphql.parse (from tracing extension)
37
+ * ├── graphql.validate (from tracing extension)
38
+ * └── graphql.resolve Query.* (from tracing middleware)
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { makeTracedGraphQLRouter } from "@effect-gql/opentelemetry"
44
+ * import { NodeSdk } from "@effect/opentelemetry"
45
+ *
46
+ * const router = makeTracedGraphQLRouter(schema, serviceLayer, {
47
+ * path: "/graphql",
48
+ * graphiql: { path: "/graphiql" },
49
+ * rootSpanName: "graphql.http",
50
+ * rootSpanAttributes: {
51
+ * "service.name": "my-api"
52
+ * }
53
+ * })
54
+ *
55
+ * // Provide OpenTelemetry layer when serving
56
+ * const TracingLayer = NodeSdk.layer(() => ({
57
+ * resource: { serviceName: "my-graphql-api" },
58
+ * spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter())
59
+ * }))
60
+ * ```
61
+ *
62
+ * @param schema - The GraphQL schema
63
+ * @param layer - Effect layer providing services required by resolvers
64
+ * @param options - Router and tracing configuration
65
+ * @returns An HttpRouter with tracing enabled
66
+ */
67
+ export declare const makeTracedGraphQLRouter: <R>(schema: GraphQLSchema, layer: Layer.Layer<R>, options?: TracedRouterOptions) => HttpRouter.HttpRouter<never, never>;
68
+ /**
69
+ * Wrap an existing HttpRouter with OpenTelemetry tracing.
70
+ *
71
+ * This is useful when you already have a router and want to add
72
+ * request-level tracing without recreating it.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * import { toRouter } from "@effect-gql/core/server"
77
+ * import { withTracedRouter } from "@effect-gql/opentelemetry"
78
+ *
79
+ * const baseRouter = toRouter(builder, serviceLayer)
80
+ * const tracedRouter = withTracedRouter(baseRouter, {
81
+ * rootSpanName: "graphql.http"
82
+ * })
83
+ * ```
84
+ */
85
+ export declare const withTracedRouter: (router: HttpRouter.HttpRouter<any, any>, options?: {
86
+ rootSpanName?: string;
87
+ rootSpanAttributes?: Record<string, string | number | boolean>;
88
+ propagateContext?: boolean;
89
+ }) => HttpRouter.HttpRouter<any, any>;
90
+ //# sourceMappingURL=traced-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traced-router.d.ts","sourceRoot":"","sources":["../src/traced-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAyC,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAU,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAG1F;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,wBAAwB;IACnE;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAE9B;;OAEG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACpC;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,EACvC,QAAQ,aAAa,EACrB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EACrB,UAAS,mBAAwB,KAChC,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAmDpC,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EACvC,UAAS;IACP,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;IAC9D,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACtB,KACL,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAuDhC,CAAA"}