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.
- package/LICENSE +191 -21
- package/README.md +2 -2
- package/dist/{config-CEF9DMGT.js → config-CFlfvmQ1.js} +2 -2
- package/dist/{config-CEF9DMGT.js.map → config-CFlfvmQ1.js.map} +1 -1
- package/dist/events.js +13 -9
- package/dist/events.js.map +1 -1
- package/dist/{execution-logger-CoxSsBmU.js → execution-logger-CSW2AyVi.js} +7 -4
- package/dist/{execution-logger-CoxSsBmU.js.map → execution-logger-CSW2AyVi.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/logger.js +7 -3
- package/dist/logger.js.map +1 -1
- package/package.json +5 -6
- package/src/api/logger.test.ts +0 -1616
- package/src/api/logger.ts +0 -1038
- package/src/api/redact.test.ts +0 -432
- package/src/api/redact.ts +0 -402
- package/src/compose.ts +0 -243
- package/src/composition.test.ts +0 -130
- package/src/composition.ts +0 -132
- package/src/core/buffer.ts +0 -17
- package/src/core/config.test.ts +0 -413
- package/src/core/config.ts +0 -169
- package/src/core/context.ts +0 -245
- package/src/core/exporter.ts +0 -99
- package/src/core/native-bridge.test.ts +0 -176
- package/src/core/native-bridge.ts +0 -278
- package/src/core/provider.context.test.ts +0 -55
- package/src/core/provider.ts +0 -46
- package/src/core/span.ts +0 -222
- package/src/core/spanprocessor.test.ts +0 -521
- package/src/core/spanprocessor.ts +0 -232
- package/src/core/trace-context.ts +0 -95
- package/src/core/tracer.test.ts +0 -123
- package/src/core/tracer.ts +0 -216
- package/src/events/index.test.ts +0 -242
- package/src/events/index.ts +0 -338
- package/src/events.ts +0 -6
- package/src/execution-logger.test.ts +0 -368
- package/src/execution-logger.ts +0 -410
- package/src/functional.test.ts +0 -827
- package/src/functional.ts +0 -1002
- package/src/index.ts +0 -140
- package/src/logger.ts +0 -35
- package/src/middleware-toolkit.test.ts +0 -71
- package/src/middleware-toolkit.ts +0 -94
- package/src/native-routing.test.ts +0 -98
- package/src/parse-error.test.ts +0 -70
- package/src/parse-error.ts +0 -110
- package/src/plugin-runner.test.ts +0 -100
- package/src/plugin-runner.ts +0 -215
- package/src/sampling/index.test.ts +0 -297
- package/src/sampling/index.ts +0 -276
- package/src/sampling.ts +0 -6
- package/src/testing/index.ts +0 -9
- package/src/testing.ts +0 -6
- package/src/types.ts +0 -325
package/src/composition.test.ts
DELETED
|
@@ -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
|
-
});
|
package/src/composition.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/buffer.ts
DELETED
|
@@ -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 };
|
package/src/core/config.test.ts
DELETED
|
@@ -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
|
-
});
|