autotel-edge 3.17.0 → 3.17.2

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.
Files changed (57) hide show
  1. package/LICENSE +191 -21
  2. package/README.md +2 -2
  3. package/dist/{config-CEF9DMGT.js → config-CFlfvmQ1.js} +2 -2
  4. package/dist/{config-CEF9DMGT.js.map → config-CFlfvmQ1.js.map} +1 -1
  5. package/dist/events.js +13 -9
  6. package/dist/events.js.map +1 -1
  7. package/dist/{execution-logger-CoxSsBmU.js → execution-logger-CSW2AyVi.js} +7 -4
  8. package/dist/{execution-logger-CoxSsBmU.js.map → execution-logger-CSW2AyVi.js.map} +1 -1
  9. package/dist/index.js +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/logger.js +7 -3
  12. package/dist/logger.js.map +1 -1
  13. package/package.json +5 -6
  14. package/src/api/logger.test.ts +0 -1616
  15. package/src/api/logger.ts +0 -1038
  16. package/src/api/redact.test.ts +0 -432
  17. package/src/api/redact.ts +0 -402
  18. package/src/compose.ts +0 -243
  19. package/src/composition.test.ts +0 -130
  20. package/src/composition.ts +0 -132
  21. package/src/core/buffer.ts +0 -17
  22. package/src/core/config.test.ts +0 -413
  23. package/src/core/config.ts +0 -169
  24. package/src/core/context.ts +0 -245
  25. package/src/core/exporter.ts +0 -99
  26. package/src/core/native-bridge.test.ts +0 -176
  27. package/src/core/native-bridge.ts +0 -278
  28. package/src/core/provider.context.test.ts +0 -55
  29. package/src/core/provider.ts +0 -46
  30. package/src/core/span.ts +0 -222
  31. package/src/core/spanprocessor.test.ts +0 -521
  32. package/src/core/spanprocessor.ts +0 -232
  33. package/src/core/trace-context.ts +0 -95
  34. package/src/core/tracer.test.ts +0 -123
  35. package/src/core/tracer.ts +0 -216
  36. package/src/events/index.test.ts +0 -242
  37. package/src/events/index.ts +0 -338
  38. package/src/events.ts +0 -6
  39. package/src/execution-logger.test.ts +0 -368
  40. package/src/execution-logger.ts +0 -410
  41. package/src/functional.test.ts +0 -827
  42. package/src/functional.ts +0 -1002
  43. package/src/index.ts +0 -140
  44. package/src/logger.ts +0 -35
  45. package/src/middleware-toolkit.test.ts +0 -71
  46. package/src/middleware-toolkit.ts +0 -94
  47. package/src/native-routing.test.ts +0 -98
  48. package/src/parse-error.test.ts +0 -70
  49. package/src/parse-error.ts +0 -110
  50. package/src/plugin-runner.test.ts +0 -100
  51. package/src/plugin-runner.ts +0 -215
  52. package/src/sampling/index.test.ts +0 -297
  53. package/src/sampling/index.ts +0 -276
  54. package/src/sampling.ts +0 -6
  55. package/src/testing/index.ts +0 -9
  56. package/src/testing.ts +0 -6
  57. package/src/types.ts +0 -325
@@ -1,130 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import {
3
- composePostProcessors,
4
- composeSpanProcessors,
5
- composeSubscribers,
6
- defineConfig,
7
- } from './composition';
8
- import type { EdgeEvent, EdgeSubscriber, PostProcessorFn, ReadableSpan } from './types';
9
-
10
- function makeSpan(name: string): ReadableSpan {
11
- return { name } as unknown as ReadableSpan;
12
- }
13
-
14
- describe('defineConfig', () => {
15
- it('returns its argument unchanged', () => {
16
- const config = defineConfig({
17
- service: { name: 'svc' },
18
- exporter: { url: 'https://otlp.example' },
19
- });
20
- expect(config.service.name).toBe('svc');
21
- });
22
- });
23
-
24
- describe('composeSubscribers', () => {
25
- it('runs subscribers in order', async () => {
26
- const calls: string[] = [];
27
- const a: EdgeSubscriber = () => {
28
- calls.push('a');
29
- };
30
- const b: EdgeSubscriber = () => {
31
- calls.push('b');
32
- };
33
-
34
- await composeSubscribers([a, b])({ kind: 'span.start' } as EdgeEvent);
35
- expect(calls).toEqual(['a', 'b']);
36
- });
37
-
38
- it('isolates errors so later subscribers still run', async () => {
39
- const calls: string[] = [];
40
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
41
-
42
- const a: EdgeSubscriber = () => {
43
- throw new Error('boom');
44
- };
45
- const b: EdgeSubscriber = () => {
46
- calls.push('b');
47
- };
48
-
49
- await composeSubscribers([a, b])({ kind: 'span.start' } as EdgeEvent);
50
-
51
- expect(calls).toEqual(['b']);
52
- expect(consoleSpy).toHaveBeenCalled();
53
- consoleSpy.mockRestore();
54
- });
55
- });
56
-
57
- describe('composePostProcessors', () => {
58
- it('threads spans through each processor', () => {
59
- const tag: PostProcessorFn = (spans) =>
60
- spans.map((s) => ({ ...s, name: `${s.name}!` } as ReadableSpan));
61
- const drop: PostProcessorFn = (spans) => spans.filter((s) => s.name !== 'b!');
62
-
63
- const result = composePostProcessors([tag, drop])([
64
- makeSpan('a'),
65
- makeSpan('b'),
66
- ]);
67
-
68
- expect(result.map((s) => s.name)).toEqual(['a!']);
69
- });
70
-
71
- it('skips a failing processor and continues with the previous output', () => {
72
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
73
-
74
- const ok: PostProcessorFn = (spans) => spans;
75
- const broken: PostProcessorFn = () => {
76
- throw new Error('bad');
77
- };
78
-
79
- const result = composePostProcessors([ok, broken, ok])([makeSpan('keep')]);
80
-
81
- expect(result.map((s) => s.name)).toEqual(['keep']);
82
- expect(consoleSpy).toHaveBeenCalled();
83
- consoleSpy.mockRestore();
84
- });
85
- });
86
-
87
- describe('composeSpanProcessors', () => {
88
- it('forwards onStart/onEnd to every processor', () => {
89
- const a = {
90
- onStart: vi.fn(),
91
- onEnd: vi.fn(),
92
- forceFlush: vi.fn().mockResolvedValue(undefined),
93
- shutdown: vi.fn().mockResolvedValue(undefined),
94
- };
95
- const b = { ...a, onStart: vi.fn(), onEnd: vi.fn() };
96
-
97
- const composed = composeSpanProcessors([a as any, b as any]);
98
- composed.onStart({} as any, {} as any);
99
- composed.onEnd({} as any);
100
-
101
- expect(a.onStart).toHaveBeenCalled();
102
- expect(b.onStart).toHaveBeenCalled();
103
- expect(a.onEnd).toHaveBeenCalled();
104
- expect(b.onEnd).toHaveBeenCalled();
105
- });
106
-
107
- it('awaits all forceFlush results even when one rejects', async () => {
108
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
109
-
110
- const a = {
111
- onStart: vi.fn(),
112
- onEnd: vi.fn(),
113
- forceFlush: vi.fn().mockRejectedValue(new Error('flush fail')),
114
- shutdown: vi.fn().mockResolvedValue(undefined),
115
- };
116
- const b = {
117
- onStart: vi.fn(),
118
- onEnd: vi.fn(),
119
- forceFlush: vi.fn().mockResolvedValue(undefined),
120
- shutdown: vi.fn().mockResolvedValue(undefined),
121
- };
122
-
123
- const composed = composeSpanProcessors([a as any, b as any]);
124
- await composed.forceFlush!();
125
-
126
- expect(a.forceFlush).toHaveBeenCalled();
127
- expect(b.forceFlush).toHaveBeenCalled();
128
- consoleSpy.mockRestore();
129
- });
130
- });
@@ -1,132 +0,0 @@
1
- /**
2
- * Composition helpers for building autotel pipelines from small, testable parts.
3
- *
4
- * The motivation is the same as for any pipeline library: you can author
5
- * subscribers, post-processors and span processors as standalone units and
6
- * stitch them together at config-construction time without inventing ad-hoc
7
- * wrappers in every package.
8
- *
9
- * None of these helpers change runtime behaviour by themselves — they only
10
- * combine functions / processors that already work in isolation.
11
- */
12
- import type { Span } from '@opentelemetry/api';
13
- import type { ReadableSpan as SdkReadableSpan, SpanProcessor } from '@opentelemetry/sdk-trace-base';
14
- import type {
15
- EdgeConfig,
16
- EdgeEvent,
17
- EdgeSubscriber,
18
- PostProcessorFn,
19
- ReadableSpan as EdgeReadableSpan,
20
- ResolveConfigFn,
21
- } from './types';
22
-
23
- type Awaitable<T> = T | Promise<T>;
24
-
25
- /**
26
- * Identity helper for authoring an autotel configuration once with full
27
- * type-checking. Mirrors `defineConfig` patterns in tools like Vite / Vitest.
28
- *
29
- * @example
30
- * ```ts
31
- * import { defineConfig } from 'autotel-edge'
32
- *
33
- * export const otelConfig = defineConfig({
34
- * service: { name: 'checkout' },
35
- * exporter: { url: process.env.OTLP_URL! },
36
- * })
37
- * ```
38
- */
39
- export function defineConfig<T extends EdgeConfig | ResolveConfigFn>(config: T): T {
40
- return config;
41
- }
42
-
43
- /**
44
- * Run a list of subscribers in registration order. Errors thrown by an
45
- * individual subscriber are caught and logged so a buggy subscriber never
46
- * blocks the others — important because `subscribers` is the primary
47
- * extensibility point for in-process side effects.
48
- */
49
- export function composeSubscribers(
50
- subscribers: Array<EdgeSubscriber>,
51
- options: { name?: string } = {},
52
- ): EdgeSubscriber {
53
- const label = options.name ?? 'compose-subscribers';
54
- return async (event: EdgeEvent) => {
55
- for (const subscriber of subscribers) {
56
- try {
57
- await subscriber(event);
58
- } catch (err) {
59
- console.error(`[autotel-edge/${label}] subscriber failed:`, err);
60
- }
61
- }
62
- };
63
- }
64
-
65
- /**
66
- * Chain post-processors so each one sees the output of the previous one.
67
- * This matches the standard OTel post-processor contract: take spans, return
68
- * spans (possibly filtered, redacted, or annotated).
69
- */
70
- export function composePostProcessors(
71
- processors: Array<PostProcessorFn>,
72
- ): PostProcessorFn {
73
- return (spans: EdgeReadableSpan[]) => {
74
- let current = spans;
75
- for (const processor of processors) {
76
- try {
77
- current = processor(current);
78
- } catch (err) {
79
- console.error('[autotel-edge/compose-post-processors] failed:', err);
80
- }
81
- }
82
- return current;
83
- };
84
- }
85
-
86
- /**
87
- * Fan out span lifecycle events to multiple span processors. Any single
88
- * processor's failure is isolated so it cannot break the others.
89
- *
90
- * Useful when you want to attach, say, a sampling processor + a custom
91
- * attribute redactor + the default batch processor without having to author a
92
- * single processor that knows about all three.
93
- */
94
- export function composeSpanProcessors(processors: SpanProcessor[]): SpanProcessor {
95
- const safe = (label: string, fn: () => Awaitable<void>) =>
96
- Promise.resolve()
97
- .then(fn)
98
- .catch((err) => {
99
- console.error(`[autotel-edge/compose-span-processors:${label}]`, err);
100
- });
101
-
102
- return {
103
- onStart(span: Span, parentContext) {
104
- for (const processor of processors) {
105
- try {
106
- processor.onStart(span as unknown as SdkReadableSpan & Span, parentContext);
107
- } catch (err) {
108
- console.error('[autotel-edge/compose-span-processors:onStart]', err);
109
- }
110
- }
111
- },
112
- onEnd(span: SdkReadableSpan) {
113
- for (const processor of processors) {
114
- try {
115
- processor.onEnd(span);
116
- } catch (err) {
117
- console.error('[autotel-edge/compose-span-processors:onEnd]', err);
118
- }
119
- }
120
- },
121
- async forceFlush() {
122
- await Promise.allSettled(
123
- processors.map((p) => safe('forceFlush', () => p.forceFlush())),
124
- );
125
- },
126
- async shutdown() {
127
- await Promise.allSettled(
128
- processors.map((p) => safe('shutdown', () => p.shutdown())),
129
- );
130
- },
131
- } as SpanProcessor;
132
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Buffer polyfill for edge environments
3
- *
4
- * Cloudflare Workers and other edge runtimes need the Buffer global
5
- * for OpenTelemetry OTLP serialization.
6
- */
7
-
8
- //@ts-ignore - node:buffer available in CF Workers with nodejs_compat
9
- import { Buffer } from 'node:buffer';
10
-
11
- //@ts-ignore
12
- globalThis.Buffer = Buffer;
13
-
14
- // Re-export the single imported binding. Importing AND re-exporting `Buffer`
15
- // from 'node:buffer' separately makes rolldown (tsdown) emit a dangling
16
- // `Buffer$1` reference, so keep exactly one binding.
17
- export { Buffer };
@@ -1,413 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { parseConfig, createInitialiser, getActiveConfig, setConfig } from './config';
3
- import type { EdgeConfig } from '../types';
4
- import { context as api_context } from '@opentelemetry/api';
5
-
6
- describe('Config System', () => {
7
- describe('parseConfig()', () => {
8
- it('should parse minimal config (only service.name)', () => {
9
- const config: EdgeConfig = {
10
- service: { name: 'test-service' },
11
- };
12
-
13
- const parsed = parseConfig(config);
14
-
15
- expect(parsed.service.name).toBe('test-service');
16
- expect(parsed.service.version).toBeUndefined();
17
- expect(parsed.service.namespace).toBeUndefined();
18
- });
19
-
20
- it('should parse full service config', () => {
21
- const config: EdgeConfig = {
22
- service: {
23
- name: 'test-service',
24
- version: '1.2.3',
25
- namespace: 'production',
26
- },
27
- };
28
-
29
- const parsed = parseConfig(config);
30
-
31
- expect(parsed.service.name).toBe('test-service');
32
- expect(parsed.service.version).toBe('1.2.3');
33
- expect(parsed.service.namespace).toBe('production');
34
- });
35
-
36
- it('should parse exporter config (URL + headers)', () => {
37
- const config: EdgeConfig = {
38
- service: { name: 'test-service' },
39
- exporter: {
40
- url: 'https://api.honeycomb.io/v1/traces',
41
- headers: { 'x-api-key': 'test-key' },
42
- },
43
- };
44
-
45
- const parsed = parseConfig(config);
46
-
47
- expect(parsed.spanProcessors).toHaveLength(1);
48
- expect(parsed.spanProcessors[0]).toBeDefined();
49
- });
50
-
51
- it('should create SpanProcessor from exporter', () => {
52
- const config: EdgeConfig = {
53
- service: { name: 'test-service' },
54
- exporter: {
55
- url: 'http://localhost:4318/v1/traces',
56
- },
57
- };
58
-
59
- const parsed = parseConfig(config);
60
-
61
- expect(parsed.spanProcessors).toBeDefined();
62
- expect(Array.isArray(parsed.spanProcessors)).toBe(true);
63
- expect(parsed.spanProcessors.length).toBeGreaterThan(0);
64
- });
65
-
66
- it('should accept custom SpanProcessor array', () => {
67
- const mockSpanProcessor = {
68
- onStart: () => {},
69
- onEnd: () => {},
70
- forceFlush: async () => {},
71
- shutdown: async () => {},
72
- };
73
-
74
- const config: EdgeConfig = {
75
- service: { name: 'test-service' },
76
- spanProcessors: [mockSpanProcessor as any],
77
- };
78
-
79
- const parsed = parseConfig(config);
80
-
81
- expect(parsed.spanProcessors).toHaveLength(1);
82
- expect(parsed.spanProcessors[0]).toBe(mockSpanProcessor);
83
- });
84
-
85
- it('should use AlwaysOnSampler as default head sampler', () => {
86
- const config: EdgeConfig = {
87
- service: { name: 'test-service' },
88
- };
89
-
90
- const parsed = parseConfig(config);
91
-
92
- expect(parsed.sampling.headSampler).toBeDefined();
93
- // Default is ParentBasedSampler with AlwaysOnSampler root
94
- expect(parsed.sampling.headSampler.toString()).toContain('ParentBased');
95
- });
96
-
97
- it('should create ParentRatioSampler from ratio config', () => {
98
- const config: EdgeConfig = {
99
- service: { name: 'test-service' },
100
- sampling: {
101
- headSampler: {
102
- ratio: 0.5,
103
- acceptRemote: true,
104
- },
105
- },
106
- };
107
-
108
- const parsed = parseConfig(config);
109
-
110
- expect(parsed.sampling.headSampler).toBeDefined();
111
- // Should be ParentBasedSampler wrapping ratio sampler
112
- expect(parsed.sampling.headSampler.toString()).toContain('ParentBased');
113
- });
114
-
115
- it('should use default tail sampler (keep sampled or errors)', () => {
116
- const config: EdgeConfig = {
117
- service: { name: 'test-service' },
118
- };
119
-
120
- const parsed = parseConfig(config);
121
-
122
- expect(parsed.sampling.tailSampler).toBeDefined();
123
- expect(typeof parsed.sampling.tailSampler).toBe('function');
124
- });
125
-
126
- it('should accept custom tail sampler', () => {
127
- const customTailSampler = () => true;
128
-
129
- const config: EdgeConfig = {
130
- service: { name: 'test-service' },
131
- sampling: {
132
- tailSampler: customTailSampler,
133
- },
134
- };
135
-
136
- const parsed = parseConfig(config);
137
-
138
- expect(parsed.sampling.tailSampler).toBe(customTailSampler);
139
- });
140
-
141
- it('should use W3CTraceContextPropagator as default', () => {
142
- const config: EdgeConfig = {
143
- service: { name: 'test-service' },
144
- };
145
-
146
- const parsed = parseConfig(config);
147
-
148
- expect(parsed.propagator).toBeDefined();
149
- expect(parsed.propagator.constructor.name).toBe('W3CTraceContextPropagator');
150
- });
151
-
152
- it('should accept custom propagator', () => {
153
- const mockPropagator = {
154
- inject: () => {},
155
- extract: () => ({} as any),
156
- fields: () => [],
157
- };
158
-
159
- const config: EdgeConfig = {
160
- service: { name: 'test-service' },
161
- propagator: mockPropagator as any,
162
- };
163
-
164
- const parsed = parseConfig(config);
165
-
166
- expect(parsed.propagator).toBe(mockPropagator);
167
- });
168
-
169
- it('should enable global fetch instrumentation by default', () => {
170
- const config: EdgeConfig = {
171
- service: { name: 'test-service' },
172
- };
173
-
174
- const parsed = parseConfig(config);
175
-
176
- expect(parsed.instrumentation.instrumentGlobalFetch).toBe(true);
177
- });
178
-
179
- it('should disable global cache instrumentation by default', () => {
180
- const config: EdgeConfig = {
181
- service: { name: 'test-service' },
182
- };
183
-
184
- const parsed = parseConfig(config);
185
-
186
- expect(parsed.instrumentation.instrumentGlobalCache).toBe(false);
187
- });
188
-
189
- it('should allow enabling cache instrumentation', () => {
190
- const config: EdgeConfig = {
191
- service: { name: 'test-service' },
192
- instrumentation: {
193
- instrumentGlobalCache: true,
194
- },
195
- };
196
-
197
- const parsed = parseConfig(config);
198
-
199
- expect(parsed.instrumentation.instrumentGlobalCache).toBe(true);
200
- });
201
-
202
- it('should use default fetch.includeTraceContext = true', () => {
203
- const config: EdgeConfig = {
204
- service: { name: 'test-service' },
205
- };
206
-
207
- const parsed = parseConfig(config);
208
-
209
- expect(parsed.fetch.includeTraceContext).toBe(true);
210
- });
211
-
212
- it('should accept custom fetch.includeTraceContext function', () => {
213
- const customFn = (request: Request) => request.url.includes('internal');
214
-
215
- const config: EdgeConfig = {
216
- service: { name: 'test-service' },
217
- fetch: {
218
- includeTraceContext: customFn,
219
- },
220
- };
221
-
222
- const parsed = parseConfig(config);
223
-
224
- expect(parsed.fetch.includeTraceContext).toBe(customFn);
225
- });
226
-
227
- it('should preserve fetch handler route filtering and service mappings', () => {
228
- const config: EdgeConfig = {
229
- service: { name: 'test-service' },
230
- handlers: {
231
- fetch: {
232
- include: ['/api/**'],
233
- exclude: ['/api/internal/**'],
234
- routes: {
235
- '/api/auth/**': { service: 'auth-service' },
236
- '/api/**': { service: 'api-service' },
237
- },
238
- },
239
- },
240
- };
241
-
242
- const parsed = parseConfig(config);
243
-
244
- expect(parsed.handlers.fetch.include).toEqual(['/api/**']);
245
- expect(parsed.handlers.fetch.exclude).toEqual(['/api/internal/**']);
246
- expect(parsed.handlers.fetch.routes).toEqual({
247
- '/api/auth/**': { service: 'auth-service' },
248
- '/api/**': { service: 'api-service' },
249
- });
250
- });
251
- });
252
-
253
- describe('createInitialiser()', () => {
254
- it('should create initialiser from static config', () => {
255
- const config: EdgeConfig = {
256
- service: { name: 'test-service' },
257
- };
258
-
259
- const initialiser = createInitialiser(config);
260
-
261
- expect(typeof initialiser).toBe('function');
262
-
263
- const resolved = initialiser({}, { request: null as any });
264
-
265
- expect(resolved.service.name).toBe('test-service');
266
- });
267
-
268
- it('should create initialiser from config function', () => {
269
- const configFn = (env: { SERVICE_NAME: string }) => ({
270
- service: { name: env.SERVICE_NAME },
271
- });
272
-
273
- const initialiser = createInitialiser(configFn);
274
-
275
- expect(typeof initialiser).toBe('function');
276
-
277
- const resolved = initialiser({ SERVICE_NAME: 'dynamic-service' }, { request: null as any });
278
-
279
- expect(resolved.service.name).toBe('dynamic-service');
280
- });
281
-
282
- it('should pass env and trigger to config function', () => {
283
- const configFn = vi.fn((env: any, trigger: any) => ({
284
- service: { name: 'test' },
285
- }));
286
-
287
- const initialiser = createInitialiser(configFn);
288
-
289
- const mockEnv = { API_KEY: 'test-key' };
290
- const mockTrigger = { request: new Request('http://example.com') };
291
-
292
- initialiser(mockEnv, mockTrigger);
293
-
294
- expect(configFn).toHaveBeenCalledWith(mockEnv, mockTrigger);
295
- });
296
- });
297
-
298
- describe('getActiveConfig() / setConfig()', () => {
299
- beforeEach(() => {
300
- // Reset active config before each test
301
- setConfig(null as any);
302
- });
303
-
304
- it('should store and retrieve active config', () => {
305
- const config: EdgeConfig = {
306
- service: { name: 'test-service' },
307
- };
308
-
309
- const parsed = parseConfig(config);
310
- const ctx = setConfig(parsed);
311
-
312
- // Use api_context.with() to activate the context
313
- api_context.with(ctx, () => {
314
- const active = getActiveConfig();
315
- expect(active).toBe(parsed);
316
- expect(active?.service.name).toBe('test-service');
317
- });
318
- });
319
-
320
- it('should return null when no active config', () => {
321
- const ctx = setConfig(null as any);
322
-
323
- api_context.with(ctx, () => {
324
- const active = getActiveConfig();
325
- expect(active).toBeNull();
326
- });
327
- });
328
-
329
- it('should allow updating active config', () => {
330
- const config1 = parseConfig({
331
- service: { name: 'service-1' },
332
- });
333
-
334
- const ctx1 = setConfig(config1);
335
- api_context.with(ctx1, () => {
336
- expect(getActiveConfig()?.service.name).toBe('service-1');
337
- });
338
-
339
- const config2 = parseConfig({
340
- service: { name: 'service-2' },
341
- });
342
-
343
- const ctx2 = setConfig(config2);
344
- api_context.with(ctx2, () => {
345
- expect(getActiveConfig()?.service.name).toBe('service-2');
346
- });
347
- });
348
- });
349
-
350
- describe('Config context isolation', () => {
351
- it('should isolate config per context using setConfig() return value', () => {
352
- const config1 = parseConfig({
353
- service: { name: 'service-1' },
354
- });
355
-
356
- const config2 = parseConfig({
357
- service: { name: 'service-2' },
358
- });
359
-
360
- // Create separate contexts
361
- const context1 = setConfig(config1);
362
- const context2 = setConfig(config2);
363
-
364
- // Verify contexts are different
365
- expect(context1).not.toBe(context2);
366
- });
367
-
368
- it('should use OpenTelemetry context for storage', () => {
369
- const config = parseConfig({
370
- service: { name: 'test-service' },
371
- });
372
-
373
- const context = setConfig(config);
374
-
375
- // setConfig should return a context object
376
- expect(context).toBeDefined();
377
- expect(typeof context).toBe('object');
378
- });
379
-
380
- it('should not have race conditions with context-based storage', () => {
381
- // This test verifies the fix for the config race condition bug
382
- // where module-level state caused request B to overwrite request A's config
383
-
384
- const configA = parseConfig({
385
- service: { name: 'request-a' },
386
- });
387
-
388
- const configB = parseConfig({
389
- service: { name: 'request-b' },
390
- });
391
-
392
- // Simulate setting configs for different requests
393
- const contextA = setConfig(configA);
394
- const contextB = setConfig(configB);
395
-
396
- // Both contexts should exist independently
397
- expect(contextA).not.toBe(contextB);
398
-
399
- // Each context has its own config that doesn't interfere with the other
400
- api_context.with(contextA, () => {
401
- const activeConfig = getActiveConfig();
402
- expect(activeConfig?.service.name).toBe('request-a');
403
- });
404
-
405
- api_context.with(contextB, () => {
406
- const activeConfig = getActiveConfig();
407
- expect(activeConfig?.service.name).toBe('request-b');
408
- });
409
-
410
- // This demonstrates that configs are properly isolated per context
411
- });
412
- });
413
- });