@atrim/instrument-node 0.1.0-7603d70-20251119002755

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,757 @@
1
+ 'use strict';
2
+
3
+ var effect = require('effect');
4
+ var Otlp = require('@effect/opentelemetry/Otlp');
5
+ var platform = require('@effect/platform');
6
+ var api = require('@opentelemetry/api');
7
+ var FileSystem = require('@effect/platform/FileSystem');
8
+ var HttpClient = require('@effect/platform/HttpClient');
9
+ var HttpClientRequest = require('@effect/platform/HttpClientRequest');
10
+ var yaml = require('yaml');
11
+ var zod = require('zod');
12
+ var platformNode = require('@effect/platform-node');
13
+
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n.default = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var Otlp__namespace = /*#__PURE__*/_interopNamespace(Otlp);
33
+ var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
34
+ var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
35
+
36
+ // src/integrations/effect/effect-tracer.ts
37
+ var __defProp = Object.defineProperty;
38
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
39
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
40
+ var PatternConfigSchema = zod.z.object({
41
+ pattern: zod.z.string(),
42
+ enabled: zod.z.boolean().optional(),
43
+ description: zod.z.string().optional()
44
+ });
45
+ var AutoIsolationConfigSchema = zod.z.object({
46
+ // Global enable/disable for auto-isolation
47
+ enabled: zod.z.boolean().default(false),
48
+ // Which operators to auto-isolate
49
+ operators: zod.z.object({
50
+ fiberset_run: zod.z.boolean().default(true),
51
+ effect_fork: zod.z.boolean().default(true),
52
+ effect_fork_daemon: zod.z.boolean().default(true),
53
+ effect_fork_in: zod.z.boolean().default(false)
54
+ }).default({}),
55
+ // Virtual parent tracking configuration
56
+ tracking: zod.z.object({
57
+ use_span_links: zod.z.boolean().default(true),
58
+ use_attributes: zod.z.boolean().default(true),
59
+ capture_logical_parent: zod.z.boolean().default(true)
60
+ }).default({}),
61
+ // Span categorization
62
+ attributes: zod.z.object({
63
+ category: zod.z.string().default("background_task"),
64
+ add_metadata: zod.z.boolean().default(true)
65
+ }).default({})
66
+ });
67
+ var HttpFilteringConfigSchema = zod.z.object({
68
+ // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
69
+ ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
70
+ // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
71
+ ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
72
+ // Require parent span for outgoing requests (prevents root spans for HTTP calls)
73
+ require_parent_for_outgoing_spans: zod.z.boolean().optional(),
74
+ // Trace context propagation configuration
75
+ // Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
76
+ propagate_trace_context: zod.z.object({
77
+ // Strategy for trace propagation
78
+ // - "all": Propagate to all cross-origin requests (may cause CORS errors)
79
+ // - "none": Never propagate trace headers
80
+ // - "same-origin": Only propagate to same-origin requests (default, safe)
81
+ // - "patterns": Propagate based on include_urls patterns
82
+ strategy: zod.z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
83
+ // URL patterns to include when strategy is "patterns"
84
+ // Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
85
+ include_urls: zod.z.array(zod.z.string()).optional()
86
+ }).optional()
87
+ });
88
+ var InstrumentationConfigSchema = zod.z.object({
89
+ version: zod.z.string(),
90
+ instrumentation: zod.z.object({
91
+ enabled: zod.z.boolean(),
92
+ description: zod.z.string().optional(),
93
+ logging: zod.z.enum(["on", "off", "minimal"]).optional().default("on"),
94
+ instrument_patterns: zod.z.array(PatternConfigSchema),
95
+ ignore_patterns: zod.z.array(PatternConfigSchema)
96
+ }),
97
+ effect: zod.z.object({
98
+ auto_extract_metadata: zod.z.boolean(),
99
+ auto_isolation: AutoIsolationConfigSchema.optional()
100
+ }).optional(),
101
+ http: HttpFilteringConfigSchema.optional()
102
+ });
103
+ (class extends effect.Data.TaggedError("ConfigError") {
104
+ get message() {
105
+ return this.reason;
106
+ }
107
+ });
108
+ var ConfigUrlError = class extends effect.Data.TaggedError("ConfigUrlError") {
109
+ get message() {
110
+ return this.reason;
111
+ }
112
+ };
113
+ var ConfigValidationError = class extends effect.Data.TaggedError("ConfigValidationError") {
114
+ get message() {
115
+ return this.reason;
116
+ }
117
+ };
118
+ var ConfigFileError = class extends effect.Data.TaggedError("ConfigFileError") {
119
+ get message() {
120
+ return this.reason;
121
+ }
122
+ };
123
+ (class extends effect.Data.TaggedError("ServiceDetectionError") {
124
+ get message() {
125
+ return this.reason;
126
+ }
127
+ });
128
+ (class extends effect.Data.TaggedError("InitializationError") {
129
+ get message() {
130
+ return this.reason;
131
+ }
132
+ });
133
+ (class extends effect.Data.TaggedError("ExportError") {
134
+ get message() {
135
+ return this.reason;
136
+ }
137
+ });
138
+ (class extends effect.Data.TaggedError("ShutdownError") {
139
+ get message() {
140
+ return this.reason;
141
+ }
142
+ });
143
+ var SECURITY_DEFAULTS = {
144
+ maxConfigSize: 1e6,
145
+ // 1MB
146
+ requestTimeout: 5e3
147
+ // 5 seconds
148
+ };
149
+ var ConfigLoader = class extends effect.Context.Tag("ConfigLoader")() {
150
+ };
151
+ var parseYamlContent = (content, uri) => effect.Effect.gen(function* () {
152
+ const parsed = yield* effect.Effect.try({
153
+ try: () => yaml.parse(content),
154
+ catch: (error) => new ConfigValidationError({
155
+ reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
156
+ cause: error
157
+ })
158
+ });
159
+ return yield* effect.Effect.try({
160
+ try: () => InstrumentationConfigSchema.parse(parsed),
161
+ catch: (error) => new ConfigValidationError({
162
+ reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
163
+ cause: error
164
+ })
165
+ });
166
+ });
167
+ var loadFromFileWithFs = (fs, path, uri) => effect.Effect.gen(function* () {
168
+ const content = yield* fs.readFileString(path).pipe(
169
+ effect.Effect.mapError(
170
+ (error) => new ConfigFileError({
171
+ reason: `Failed to read config file at ${uri}`,
172
+ cause: error
173
+ })
174
+ )
175
+ );
176
+ if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
177
+ return yield* effect.Effect.fail(
178
+ new ConfigFileError({
179
+ reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
180
+ })
181
+ );
182
+ }
183
+ return yield* parseYamlContent(content, uri);
184
+ });
185
+ var loadFromHttpWithClient = (client, url) => effect.Effect.scoped(
186
+ effect.Effect.gen(function* () {
187
+ if (url.startsWith("http://")) {
188
+ return yield* effect.Effect.fail(
189
+ new ConfigUrlError({
190
+ reason: "Insecure protocol: only HTTPS URLs are allowed"
191
+ })
192
+ );
193
+ }
194
+ const request = HttpClientRequest__namespace.get(url).pipe(
195
+ HttpClientRequest__namespace.setHeaders({
196
+ Accept: "application/yaml, text/yaml, application/x-yaml"
197
+ })
198
+ );
199
+ const response = yield* client.execute(request).pipe(
200
+ effect.Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
201
+ effect.Effect.mapError((error) => {
202
+ if (error._tag === "TimeoutException") {
203
+ return new ConfigUrlError({
204
+ reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
205
+ });
206
+ }
207
+ return new ConfigUrlError({
208
+ reason: `Failed to load config from URL: ${url}`,
209
+ cause: error
210
+ });
211
+ })
212
+ );
213
+ if (response.status >= 400) {
214
+ return yield* effect.Effect.fail(
215
+ new ConfigUrlError({
216
+ reason: `HTTP ${response.status} from ${url}`
217
+ })
218
+ );
219
+ }
220
+ const text = yield* response.text.pipe(
221
+ effect.Effect.mapError(
222
+ (error) => new ConfigUrlError({
223
+ reason: `Failed to read response body from ${url}`,
224
+ cause: error
225
+ })
226
+ )
227
+ );
228
+ if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
229
+ return yield* effect.Effect.fail(
230
+ new ConfigUrlError({
231
+ reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
232
+ })
233
+ );
234
+ }
235
+ return yield* parseYamlContent(text, url);
236
+ })
237
+ );
238
+ var makeConfigLoader = effect.Effect.gen(function* () {
239
+ const fs = yield* effect.Effect.serviceOption(FileSystem.FileSystem);
240
+ const http = yield* HttpClient__namespace.HttpClient;
241
+ const loadFromUriUncached = (uri) => effect.Effect.gen(function* () {
242
+ if (uri.startsWith("file://")) {
243
+ const path = uri.slice(7);
244
+ if (fs._tag === "None") {
245
+ return yield* effect.Effect.fail(
246
+ new ConfigFileError({
247
+ reason: "FileSystem not available (browser environment?)",
248
+ cause: { uri }
249
+ })
250
+ );
251
+ }
252
+ return yield* loadFromFileWithFs(fs.value, path, uri);
253
+ }
254
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
255
+ return yield* loadFromHttpWithClient(http, uri);
256
+ }
257
+ if (fs._tag === "Some") {
258
+ return yield* loadFromFileWithFs(fs.value, uri, uri);
259
+ } else {
260
+ return yield* loadFromHttpWithClient(http, uri);
261
+ }
262
+ });
263
+ const loadFromUriCached = yield* effect.Effect.cachedFunction(loadFromUriUncached);
264
+ return ConfigLoader.of({
265
+ loadFromUri: loadFromUriCached,
266
+ loadFromInline: (content) => effect.Effect.gen(function* () {
267
+ if (typeof content === "string") {
268
+ return yield* parseYamlContent(content);
269
+ }
270
+ return yield* effect.Effect.try({
271
+ try: () => InstrumentationConfigSchema.parse(content),
272
+ catch: (error) => new ConfigValidationError({
273
+ reason: "Invalid configuration schema",
274
+ cause: error
275
+ })
276
+ });
277
+ })
278
+ });
279
+ });
280
+ var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
281
+ var PatternMatcher = class {
282
+ constructor(config) {
283
+ __publicField(this, "ignorePatterns", []);
284
+ __publicField(this, "instrumentPatterns", []);
285
+ __publicField(this, "enabled", true);
286
+ this.enabled = config.instrumentation.enabled;
287
+ this.ignorePatterns = config.instrumentation.ignore_patterns.map((p) => this.compilePattern(p));
288
+ this.instrumentPatterns = config.instrumentation.instrument_patterns.filter((p) => p.enabled !== false).map((p) => this.compilePattern(p));
289
+ }
290
+ /**
291
+ * Compile a pattern configuration into a RegExp
292
+ */
293
+ compilePattern(pattern) {
294
+ try {
295
+ const compiled = {
296
+ regex: new RegExp(pattern.pattern),
297
+ enabled: pattern.enabled !== false
298
+ };
299
+ if (pattern.description !== void 0) {
300
+ compiled.description = pattern.description;
301
+ }
302
+ return compiled;
303
+ } catch (error) {
304
+ throw new Error(
305
+ `Failed to compile pattern "${pattern.pattern}": ${error instanceof Error ? error.message : String(error)}`
306
+ );
307
+ }
308
+ }
309
+ /**
310
+ * Check if a span should be instrumented
311
+ *
312
+ * Returns true if the span should be created, false otherwise.
313
+ *
314
+ * Logic:
315
+ * 1. If instrumentation disabled globally, return false
316
+ * 2. Check ignore patterns - if any match, return false
317
+ * 3. Check instrument patterns - if any match, return true
318
+ * 4. Default: return true (fail-open - create span if no patterns match)
319
+ */
320
+ shouldInstrument(spanName) {
321
+ if (!this.enabled) {
322
+ return false;
323
+ }
324
+ for (const pattern of this.ignorePatterns) {
325
+ if (pattern.regex.test(spanName)) {
326
+ return false;
327
+ }
328
+ }
329
+ for (const pattern of this.instrumentPatterns) {
330
+ if (pattern.enabled && pattern.regex.test(spanName)) {
331
+ return true;
332
+ }
333
+ }
334
+ return true;
335
+ }
336
+ /**
337
+ * Get statistics about pattern matching (for debugging/monitoring)
338
+ */
339
+ getStats() {
340
+ return {
341
+ enabled: this.enabled,
342
+ ignorePatternCount: this.ignorePatterns.length,
343
+ instrumentPatternCount: this.instrumentPatterns.filter((p) => p.enabled).length
344
+ };
345
+ }
346
+ };
347
+ function initializePatternMatcher(config) {
348
+ new PatternMatcher(config);
349
+ }
350
+ var Logger = class {
351
+ constructor() {
352
+ __publicField(this, "level", "on");
353
+ __publicField(this, "hasLoggedMinimal", false);
354
+ }
355
+ /**
356
+ * Set the logging level
357
+ */
358
+ setLevel(level) {
359
+ this.level = level;
360
+ this.hasLoggedMinimal = false;
361
+ }
362
+ /**
363
+ * Get the current logging level
364
+ */
365
+ getLevel() {
366
+ return this.level;
367
+ }
368
+ /**
369
+ * Log a minimal initialization message (only shown once in minimal mode)
370
+ */
371
+ minimal(message) {
372
+ if (this.level === "off") {
373
+ return;
374
+ }
375
+ if (this.level === "minimal" && !this.hasLoggedMinimal) {
376
+ console.log(message);
377
+ this.hasLoggedMinimal = true;
378
+ return;
379
+ }
380
+ if (this.level === "on") {
381
+ console.log(message);
382
+ }
383
+ }
384
+ /**
385
+ * Log an informational message
386
+ */
387
+ log(...args) {
388
+ if (this.level === "on") {
389
+ console.log(...args);
390
+ }
391
+ }
392
+ /**
393
+ * Log a warning message (shown in minimal mode)
394
+ */
395
+ warn(...args) {
396
+ if (this.level !== "off") {
397
+ console.warn(...args);
398
+ }
399
+ }
400
+ /**
401
+ * Log an error message (shown in minimal mode)
402
+ */
403
+ error(...args) {
404
+ if (this.level !== "off") {
405
+ console.error(...args);
406
+ }
407
+ }
408
+ /**
409
+ * Check if full logging is enabled
410
+ */
411
+ isEnabled() {
412
+ return this.level === "on";
413
+ }
414
+ /**
415
+ * Check if minimal logging is enabled
416
+ */
417
+ isMinimal() {
418
+ return this.level === "minimal";
419
+ }
420
+ /**
421
+ * Check if logging is completely disabled
422
+ */
423
+ isDisabled() {
424
+ return this.level === "off";
425
+ }
426
+ };
427
+ var logger = new Logger();
428
+ var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
429
+ effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
430
+ );
431
+ var cachedLoaderPromise = null;
432
+ function getCachedLoader() {
433
+ if (!cachedLoaderPromise) {
434
+ cachedLoaderPromise = effect.Effect.runPromise(
435
+ effect.Effect.gen(function* () {
436
+ return yield* ConfigLoader;
437
+ }).pipe(effect.Effect.provide(NodeConfigLoaderLive))
438
+ );
439
+ }
440
+ return cachedLoaderPromise;
441
+ }
442
+ async function loadConfig(uri, options) {
443
+ if (options?.cacheTimeout === 0) {
444
+ const program = effect.Effect.gen(function* () {
445
+ const loader2 = yield* ConfigLoader;
446
+ return yield* loader2.loadFromUri(uri);
447
+ });
448
+ return effect.Effect.runPromise(program.pipe(effect.Effect.provide(NodeConfigLoaderLive)));
449
+ }
450
+ const loader = await getCachedLoader();
451
+ return effect.Effect.runPromise(loader.loadFromUri(uri));
452
+ }
453
+ async function loadConfigFromInline(content) {
454
+ const loader = await getCachedLoader();
455
+ return effect.Effect.runPromise(loader.loadFromInline(content));
456
+ }
457
+ function getDefaultConfig() {
458
+ return {
459
+ version: "1.0",
460
+ instrumentation: {
461
+ enabled: true,
462
+ logging: "on",
463
+ description: "Default instrumentation configuration",
464
+ instrument_patterns: [
465
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
466
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
467
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
468
+ ],
469
+ ignore_patterns: [
470
+ { pattern: "^test\\.", description: "Test utilities" },
471
+ { pattern: "^internal\\.", description: "Internal operations" },
472
+ { pattern: "^health\\.", description: "Health checks" }
473
+ ]
474
+ },
475
+ effect: {
476
+ auto_extract_metadata: true
477
+ }
478
+ };
479
+ }
480
+ async function loadConfigWithOptions(options = {}) {
481
+ const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
482
+ if (options.config) {
483
+ return loadConfigFromInline(options.config);
484
+ }
485
+ const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
486
+ if (envConfigPath) {
487
+ return loadConfig(envConfigPath, loadOptions);
488
+ }
489
+ if (options.configUrl) {
490
+ return loadConfig(options.configUrl, loadOptions);
491
+ }
492
+ if (options.configPath) {
493
+ return loadConfig(options.configPath, loadOptions);
494
+ }
495
+ const { existsSync } = await import('fs');
496
+ const { join } = await import('path');
497
+ const defaultPath = join(process.cwd(), "instrumentation.yaml");
498
+ if (existsSync(defaultPath)) {
499
+ return loadConfig(defaultPath, loadOptions);
500
+ }
501
+ return getDefaultConfig();
502
+ }
503
+
504
+ // src/integrations/effect/effect-tracer.ts
505
+ function createEffectInstrumentation(options = {}) {
506
+ return effect.Layer.unwrapEffect(
507
+ effect.Effect.gen(function* () {
508
+ const config = yield* effect.Effect.tryPromise({
509
+ try: () => loadConfigWithOptions(options),
510
+ catch: (error) => ({
511
+ _tag: "ConfigError",
512
+ message: error instanceof Error ? error.message : String(error)
513
+ })
514
+ });
515
+ yield* effect.Effect.sync(() => {
516
+ const loggingLevel = config.instrumentation.logging || "on";
517
+ logger.setLevel(loggingLevel);
518
+ });
519
+ yield* effect.Effect.sync(() => initializePatternMatcher(config));
520
+ const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
521
+ const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
522
+ const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
523
+ const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
524
+ const continueExistingTraces = options.continueExistingTraces ?? true;
525
+ logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
526
+ logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
527
+ logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
528
+ logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
529
+ logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
530
+ const otlpLayer = Otlp__namespace.layer({
531
+ baseUrl: otlpEndpoint,
532
+ resource: {
533
+ serviceName,
534
+ serviceVersion,
535
+ attributes: {
536
+ "platform.component": "effect",
537
+ "effect.auto_metadata": autoExtractMetadata,
538
+ "effect.context_propagation": continueExistingTraces
539
+ }
540
+ },
541
+ // Bridge Effect context to OpenTelemetry global context
542
+ // This is essential for context propagation to work properly
543
+ tracerContext: (f, span) => {
544
+ if (span._tag !== "Span") {
545
+ return f();
546
+ }
547
+ const spanContext = {
548
+ traceId: span.traceId,
549
+ spanId: span.spanId,
550
+ traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
551
+ };
552
+ const otelSpan = api.trace.wrapSpanContext(spanContext);
553
+ return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
554
+ }
555
+ }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
556
+ if (autoExtractMetadata) {
557
+ return otlpLayer;
558
+ }
559
+ return otlpLayer;
560
+ })
561
+ ).pipe(effect.Layer.orDie);
562
+ }
563
+ var EffectInstrumentationLive = effect.Effect.sync(() => {
564
+ const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
565
+ const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
566
+ const serviceVersion = process.env.npm_package_version || "1.0.0";
567
+ logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
568
+ logger.log("\u{1F50D} Effect OpenTelemetry tracer");
569
+ logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
570
+ logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
571
+ return Otlp__namespace.layer({
572
+ baseUrl: endpoint,
573
+ resource: {
574
+ serviceName,
575
+ serviceVersion,
576
+ attributes: {
577
+ "platform.component": "effect"
578
+ }
579
+ },
580
+ // CRITICAL: Bridge Effect context to OpenTelemetry global context
581
+ // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
582
+ tracerContext: (f, span) => {
583
+ if (span._tag !== "Span") {
584
+ return f();
585
+ }
586
+ const spanContext = {
587
+ traceId: span.traceId,
588
+ spanId: span.spanId,
589
+ traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
590
+ };
591
+ const otelSpan = api.trace.wrapSpanContext(spanContext);
592
+ return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
593
+ }
594
+ }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
595
+ }).pipe(effect.Layer.unwrapEffect);
596
+
597
+ // src/integrations/effect/effect-helpers.ts
598
+ function annotateUser(_userId, _email) {
599
+ }
600
+ function annotateDataSize(_bytes, _count) {
601
+ }
602
+ function annotateBatch(_size, _batchSize) {
603
+ }
604
+ function annotateLLM(_model, _operation, _inputTokens, _outputTokens) {
605
+ }
606
+ function annotateQuery(_query, _database) {
607
+ }
608
+ function annotateHttpRequest(_method, _url, _statusCode) {
609
+ }
610
+ function annotateError(_error, _context) {
611
+ }
612
+ function annotatePriority(_priority) {
613
+ }
614
+ function annotateCache(_operation, _hit) {
615
+ }
616
+ var createLogicalParentLink = (parentSpan, useSpanLinks) => {
617
+ if (!useSpanLinks) {
618
+ return [];
619
+ }
620
+ return [
621
+ {
622
+ _tag: "SpanLink",
623
+ span: parentSpan,
624
+ attributes: {
625
+ "link.type": "logical_parent",
626
+ "atrim.relationship": "spawned_by",
627
+ description: "Logical parent (isolated to prevent context leakage)"
628
+ }
629
+ }
630
+ ];
631
+ };
632
+ var createLogicalParentAttributes = (parentSpan, useAttributes, category, useSpanLinks, customAttributes) => {
633
+ if (!useAttributes) {
634
+ return customAttributes;
635
+ }
636
+ return {
637
+ // Logical parent tracking (works in ALL tools)
638
+ "atrim.logical_parent.span_id": parentSpan.spanId,
639
+ "atrim.logical_parent.trace_id": parentSpan.traceId,
640
+ "atrim.logical_parent.name": parentSpan._tag === "Span" ? parentSpan.name : "external",
641
+ // Categorization and metadata
642
+ "atrim.fiberset.isolated": true,
643
+ "atrim.span.category": category,
644
+ "atrim.has_logical_parent": true,
645
+ "atrim.isolation.method": useSpanLinks ? "hybrid" : "attributes_only",
646
+ // User-provided attributes
647
+ ...customAttributes
648
+ };
649
+ };
650
+ var runIsolated = (set, effect$1, name, options) => {
651
+ const {
652
+ createRoot = true,
653
+ captureLogicalParent = true,
654
+ useSpanLinks = true,
655
+ useAttributes = true,
656
+ propagateInterruption,
657
+ attributes = {},
658
+ category = "background_task"
659
+ } = options ?? {};
660
+ if (!createRoot && !captureLogicalParent) {
661
+ return effect.FiberSet.run(set, effect$1, { propagateInterruption });
662
+ }
663
+ return effect.Effect.gen(function* () {
664
+ const maybeParent = yield* effect.Effect.serviceOption(effect.Tracer.ParentSpan);
665
+ if (maybeParent._tag === "None" || !captureLogicalParent) {
666
+ const isolated2 = effect$1.pipe(
667
+ effect.Effect.withSpan(name, {
668
+ root: createRoot,
669
+ attributes: {
670
+ "atrim.fiberset.isolated": createRoot,
671
+ "atrim.span.category": category,
672
+ "atrim.has_logical_parent": false,
673
+ ...attributes
674
+ }
675
+ })
676
+ );
677
+ return yield* effect.FiberSet.run(set, isolated2, { propagateInterruption });
678
+ }
679
+ const parent = maybeParent.value;
680
+ const links = createLogicalParentLink(parent, useSpanLinks);
681
+ const spanAttributes = createLogicalParentAttributes(
682
+ parent,
683
+ useAttributes,
684
+ category,
685
+ useSpanLinks,
686
+ attributes
687
+ );
688
+ const isolated = effect$1.pipe(
689
+ effect.Effect.withSpan(name, {
690
+ root: createRoot,
691
+ links: links.length > 0 ? links : void 0,
692
+ attributes: spanAttributes
693
+ })
694
+ );
695
+ return yield* effect.FiberSet.run(set, isolated, { propagateInterruption });
696
+ });
697
+ };
698
+ var runWithSpan = (set, name, effect, options) => {
699
+ return runIsolated(set, effect, name, {
700
+ createRoot: true,
701
+ captureLogicalParent: true,
702
+ useSpanLinks: true,
703
+ useAttributes: true,
704
+ ...options
705
+ });
706
+ };
707
+ var annotateSpawnedTasks = (tasks) => {
708
+ return effect.Effect.annotateCurrentSpan({
709
+ "atrim.fiberset.spawned_count": tasks.length,
710
+ "atrim.fiberset.task_names": tasks.map((t) => t.name).join(","),
711
+ "atrim.has_background_tasks": true,
712
+ "atrim.spawned_tasks": JSON.stringify(
713
+ tasks.map((t) => ({
714
+ name: t.name,
715
+ ...t.spanId && { span_id: t.spanId },
716
+ ...t.category && { category: t.category }
717
+ }))
718
+ )
719
+ });
720
+ };
721
+ var FiberSet = {
722
+ // Re-export all original FiberSet functions
723
+ make: effect.FiberSet.make,
724
+ add: effect.FiberSet.add,
725
+ unsafeAdd: effect.FiberSet.unsafeAdd,
726
+ run: effect.FiberSet.run,
727
+ clear: effect.FiberSet.clear,
728
+ join: effect.FiberSet.join,
729
+ awaitEmpty: effect.FiberSet.awaitEmpty,
730
+ size: effect.FiberSet.size,
731
+ runtime: effect.FiberSet.runtime,
732
+ runtimePromise: effect.FiberSet.runtimePromise,
733
+ makeRuntime: effect.FiberSet.makeRuntime,
734
+ makeRuntimePromise: effect.FiberSet.makeRuntimePromise,
735
+ isFiberSet: effect.FiberSet.isFiberSet,
736
+ // Add our isolation helpers
737
+ runIsolated,
738
+ runWithSpan
739
+ };
740
+
741
+ exports.EffectInstrumentationLive = EffectInstrumentationLive;
742
+ exports.FiberSet = FiberSet;
743
+ exports.annotateBatch = annotateBatch;
744
+ exports.annotateCache = annotateCache;
745
+ exports.annotateDataSize = annotateDataSize;
746
+ exports.annotateError = annotateError;
747
+ exports.annotateHttpRequest = annotateHttpRequest;
748
+ exports.annotateLLM = annotateLLM;
749
+ exports.annotatePriority = annotatePriority;
750
+ exports.annotateQuery = annotateQuery;
751
+ exports.annotateSpawnedTasks = annotateSpawnedTasks;
752
+ exports.annotateUser = annotateUser;
753
+ exports.createEffectInstrumentation = createEffectInstrumentation;
754
+ exports.runIsolated = runIsolated;
755
+ exports.runWithSpan = runWithSpan;
756
+ //# sourceMappingURL=index.cjs.map
757
+ //# sourceMappingURL=index.cjs.map