autotel-cloudflare 3.0.0 → 4.0.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.
Files changed (73) hide show
  1. package/dist/agents.d.ts +626 -123
  2. package/dist/agents.d.ts.map +1 -1
  3. package/dist/agents.js +260 -171
  4. package/dist/agents.js.map +1 -1
  5. package/package.json +8 -4
  6. package/src/actors/alarms.ts +0 -225
  7. package/src/actors/index.ts +0 -36
  8. package/src/actors/instrument-actor.test.ts +0 -179
  9. package/src/actors/instrument-actor.ts +0 -574
  10. package/src/actors/sockets.ts +0 -217
  11. package/src/actors/storage.ts +0 -263
  12. package/src/actors/traced-handler.ts +0 -300
  13. package/src/actors/types.ts +0 -98
  14. package/src/actors.ts +0 -50
  15. package/src/agents/index.ts +0 -42
  16. package/src/agents/otel-observability.test.ts +0 -336
  17. package/src/agents/otel-observability.ts +0 -474
  18. package/src/agents/types.ts +0 -167
  19. package/src/agents.ts +0 -76
  20. package/src/bindings/ai.test.ts +0 -170
  21. package/src/bindings/ai.ts +0 -73
  22. package/src/bindings/analytics-engine.test.ts +0 -175
  23. package/src/bindings/analytics-engine.ts +0 -78
  24. package/src/bindings/bindings-cache.test.ts +0 -80
  25. package/src/bindings/bindings-detection.test.ts +0 -235
  26. package/src/bindings/bindings-this-binding.test.ts +0 -294
  27. package/src/bindings/bindings.ts +0 -720
  28. package/src/bindings/browser-rendering.test.ts +0 -160
  29. package/src/bindings/browser-rendering.ts +0 -70
  30. package/src/bindings/common.ts +0 -84
  31. package/src/bindings/hyperdrive.test.ts +0 -176
  32. package/src/bindings/hyperdrive.ts +0 -74
  33. package/src/bindings/images.test.ts +0 -262
  34. package/src/bindings/images.ts +0 -182
  35. package/src/bindings/index.ts +0 -20
  36. package/src/bindings/queue-producer.test.ts +0 -224
  37. package/src/bindings/queue-producer.ts +0 -105
  38. package/src/bindings/rate-limiter.test.ts +0 -140
  39. package/src/bindings/rate-limiter.ts +0 -69
  40. package/src/bindings/vectorize.test.ts +0 -362
  41. package/src/bindings/vectorize.ts +0 -86
  42. package/src/bindings.ts +0 -6
  43. package/src/events.ts +0 -6
  44. package/src/execution-logger.test.ts +0 -127
  45. package/src/execution-logger.ts +0 -112
  46. package/src/global/cache.test.ts +0 -292
  47. package/src/global/cache.ts +0 -164
  48. package/src/global/fetch.test.ts +0 -399
  49. package/src/global/fetch.ts +0 -136
  50. package/src/global/index.ts +0 -7
  51. package/src/handlers/durable-objects.test.ts +0 -524
  52. package/src/handlers/durable-objects.ts +0 -250
  53. package/src/handlers/index.ts +0 -6
  54. package/src/handlers/workflows.test.ts +0 -411
  55. package/src/handlers/workflows.ts +0 -345
  56. package/src/handlers.ts +0 -6
  57. package/src/index.ts +0 -91
  58. package/src/logger.ts +0 -15
  59. package/src/native/native-tracing.test.ts +0 -54
  60. package/src/native/native-tracing.ts +0 -83
  61. package/src/native.ts +0 -12
  62. package/src/parse-error.ts +0 -1
  63. package/src/sampling.ts +0 -6
  64. package/src/testing.ts +0 -6
  65. package/src/wrappers/cf-attributes.test.ts +0 -334
  66. package/src/wrappers/define-worker-fetch.test.ts +0 -102
  67. package/src/wrappers/define-worker-fetch.ts +0 -71
  68. package/src/wrappers/index.ts +0 -13
  69. package/src/wrappers/instrument.integration.test.ts +0 -578
  70. package/src/wrappers/instrument.ts +0 -806
  71. package/src/wrappers/native-instrument.test.ts +0 -153
  72. package/src/wrappers/wrap-do.ts +0 -34
  73. package/src/wrappers/wrap-module.ts +0 -37
@@ -1,262 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
3
- import { instrumentImages } from './images';
4
-
5
- describe('Images Binding Instrumentation', () => {
6
- let mockTracer: any;
7
- let mockSpan: any;
8
- let getTracerSpy: any;
9
-
10
- beforeEach(() => {
11
- mockSpan = {
12
- spanContext: () => ({
13
- traceId: 'test-trace-id',
14
- spanId: 'test-span-id',
15
- traceFlags: 1,
16
- }),
17
- setAttribute: vi.fn(),
18
- setAttributes: vi.fn(),
19
- setStatus: vi.fn(),
20
- recordException: vi.fn(),
21
- end: vi.fn(),
22
- isRecording: () => true,
23
- updateName: vi.fn(),
24
- addEvent: vi.fn(),
25
- };
26
-
27
- mockTracer = {
28
- startActiveSpan: vi.fn((name, options, fn) => {
29
- return fn(mockSpan);
30
- }),
31
- };
32
-
33
- getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
34
- });
35
-
36
- afterEach(() => {
37
- getTracerSpy.mockRestore();
38
- });
39
-
40
- function createMockImages() {
41
- const mockTransformer: any = {
42
- transform: vi.fn(function (this: any) {
43
- return mockTransformer;
44
- }),
45
- draw: vi.fn(function (this: any) {
46
- return mockTransformer;
47
- }),
48
- output: vi.fn(async () => ({
49
- response: () => new Response('image-data'),
50
- blob: async () => new Blob(['image-data']),
51
- arrayBuffer: async () => new ArrayBuffer(8),
52
- })),
53
- };
54
-
55
- return {
56
- images: {
57
- info: vi.fn(async () => ({ width: 800, height: 600, format: 'png' })),
58
- input: vi.fn(() => mockTransformer),
59
- someOtherMethod: vi.fn(() => 'passthrough'),
60
- someProperty: 'test-value',
61
- },
62
- mockTransformer,
63
- };
64
- }
65
-
66
- describe('info()', () => {
67
- it('should create its own span with correct attributes', async () => {
68
- const { images } = createMockImages();
69
- const instrumented = instrumentImages(images as any, 'my-images');
70
-
71
- await instrumented.info(new ArrayBuffer(8));
72
-
73
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
74
-
75
- const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
76
- expect(spanName).toBe('Images my-images: info');
77
- expect(options.kind).toBe(SpanKind.CLIENT);
78
- expect(options.attributes['images.system']).toBe('cloudflare-images');
79
- expect(options.attributes['images.operation']).toBe('info');
80
- });
81
-
82
- it('should record width, height, format from result', async () => {
83
- const { images } = createMockImages();
84
- const instrumented = instrumentImages(images as any, 'my-images');
85
-
86
- await instrumented.info(new ArrayBuffer(8));
87
-
88
- expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.width', 800);
89
- expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.height', 600);
90
- expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.format', 'png');
91
- expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
92
- expect(mockSpan.end).toHaveBeenCalled();
93
- });
94
-
95
- it('should handle errors', async () => {
96
- const { images } = createMockImages();
97
- const testError = new Error('Image info failed');
98
- images.info = vi.fn(async () => {
99
- throw testError;
100
- });
101
- const instrumented = instrumentImages(images as any, 'my-images');
102
-
103
- await expect(instrumented.info(new ArrayBuffer(8))).rejects.toThrow('Image info failed');
104
-
105
- expect(mockSpan.recordException).toHaveBeenCalledWith(testError);
106
- expect(mockSpan.setStatus).toHaveBeenCalledWith({
107
- code: SpanStatusCode.ERROR,
108
- message: 'Image info failed',
109
- });
110
- expect(mockSpan.end).toHaveBeenCalled();
111
- });
112
- });
113
-
114
- describe('pipeline: input() -> output()', () => {
115
- it('should create a single span at output() with operation_count = 0', async () => {
116
- const { images } = createMockImages();
117
- const instrumented = instrumentImages(images as any, 'my-images');
118
-
119
- const transformer = instrumented.input(new ArrayBuffer(8));
120
- await transformer.output();
121
-
122
- // Only one span created at output(), not at input()
123
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
124
-
125
- const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
126
- expect(spanName).toBe('Images my-images: output');
127
- expect(options.attributes['images.system']).toBe('cloudflare-images');
128
- expect(options.attributes['images.pipeline.operation_count']).toBe(0);
129
- });
130
- });
131
-
132
- describe('pipeline: input() -> transform() -> output()', () => {
133
- it('should create span with operation_count = 1', async () => {
134
- const { images } = createMockImages();
135
- const instrumented = instrumentImages(images as any, 'my-images');
136
-
137
- const transformer = instrumented.input(new ArrayBuffer(8));
138
- const transformed = transformer.transform({ width: 400 });
139
- await transformed.output();
140
-
141
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
142
-
143
- const [, options] = mockTracer.startActiveSpan.mock.calls[0];
144
- expect(options.attributes['images.pipeline.operation_count']).toBe(1);
145
- });
146
- });
147
-
148
- describe('pipeline: input() -> transform() -> draw() -> output()', () => {
149
- it('should create span with operation_count = 2', async () => {
150
- const { images } = createMockImages();
151
- const instrumented = instrumentImages(images as any, 'my-images');
152
-
153
- const transformer = instrumented.input(new ArrayBuffer(8));
154
- const transformed = transformer.transform({ width: 400 });
155
- const drawn = transformed.draw({});
156
- await drawn.output();
157
-
158
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
159
-
160
- const [, options] = mockTracer.startActiveSpan.mock.calls[0];
161
- expect(options.attributes['images.pipeline.operation_count']).toBe(2);
162
- });
163
- });
164
-
165
- describe('output() format capture', () => {
166
- it('should capture format from string arg', async () => {
167
- const { images } = createMockImages();
168
- const instrumented = instrumentImages(images as any, 'my-images');
169
-
170
- const transformer = instrumented.input(new ArrayBuffer(8));
171
- await transformer.output('webp');
172
-
173
- const [, options] = mockTracer.startActiveSpan.mock.calls[0];
174
- expect(options.attributes['images.output.format']).toBe('webp');
175
- });
176
-
177
- it('should capture format from options object', async () => {
178
- const { images } = createMockImages();
179
- const instrumented = instrumentImages(images as any, 'my-images');
180
-
181
- const transformer = instrumented.input(new ArrayBuffer(8));
182
- await transformer.output({ format: 'avif' });
183
-
184
- const [, options] = mockTracer.startActiveSpan.mock.calls[0];
185
- expect(options.attributes['images.output.format']).toBe('avif');
186
- });
187
-
188
- it('should handle errors in output()', async () => {
189
- const { images, mockTransformer } = createMockImages();
190
- const testError = new Error('Output failed');
191
- mockTransformer.output = vi.fn(async () => {
192
- throw testError;
193
- });
194
-
195
- const instrumented = instrumentImages(images as any, 'my-images');
196
- const transformer = instrumented.input(new ArrayBuffer(8));
197
-
198
- await expect(transformer.output()).rejects.toThrow('Output failed');
199
-
200
- expect(mockSpan.recordException).toHaveBeenCalledWith(testError);
201
- expect(mockSpan.setStatus).toHaveBeenCalledWith({
202
- code: SpanStatusCode.ERROR,
203
- message: 'Output failed',
204
- });
205
- expect(mockSpan.end).toHaveBeenCalled();
206
- });
207
- });
208
-
209
- describe('this-binding', () => {
210
- it('should invoke info() with original object as this, not the proxy', async () => {
211
- let receivedThis: any;
212
- const mockImagesObj = {
213
- info: vi.fn(async function(this: any) {
214
- // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
215
- receivedThis = this;
216
- return { width: 800, height: 600, format: 'png' };
217
- }),
218
- input: vi.fn(() => ({ transform: vi.fn(), draw: vi.fn(), output: vi.fn() })),
219
- };
220
- const instrumented = instrumentImages(mockImagesObj as any, 'test');
221
- await instrumented.info(new ArrayBuffer(8));
222
- expect(receivedThis).toBe(mockImagesObj);
223
- });
224
-
225
- it('should invoke input() with original object as this, not the proxy', () => {
226
- let receivedThis: any;
227
- const mockTransformer = { transform: vi.fn(), draw: vi.fn(), output: vi.fn(async () => ({})) };
228
- const mockImagesObj = {
229
- info: vi.fn(async () => ({ width: 800, height: 600, format: 'png' })),
230
- input: vi.fn(function(this: any) {
231
- // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
232
- receivedThis = this;
233
- return mockTransformer;
234
- }),
235
- };
236
- const instrumented = instrumentImages(mockImagesObj as any, 'test');
237
- instrumented.input(new ArrayBuffer(8));
238
- expect(receivedThis).toBe(mockImagesObj);
239
- });
240
- });
241
-
242
- describe('non-instrumented methods', () => {
243
- it('should pass through non-instrumented methods unchanged', () => {
244
- const { images } = createMockImages();
245
- const instrumented = instrumentImages(images as any, 'my-images');
246
-
247
- const result = (instrumented as any).someOtherMethod();
248
-
249
- expect(result).toBe('passthrough');
250
- expect(images.someOtherMethod).toHaveBeenCalled();
251
- expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
252
- });
253
-
254
- it('should pass through non-instrumented properties unchanged', () => {
255
- const { images } = createMockImages();
256
- const instrumented = instrumentImages(images as any, 'my-images');
257
-
258
- expect((instrumented as any).someProperty).toBe('test-value');
259
- expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
260
- });
261
- });
262
- });
@@ -1,182 +0,0 @@
1
- /**
2
- * Images binding instrumentation
3
- *
4
- * The Images binding uses a fluent chain: input() -> transform() -> draw() -> output()
5
- * We only create a span at the terminal output() call to avoid intermediate noise.
6
- * info() is a standalone operation and gets its own span.
7
- */
8
-
9
- import {
10
- trace,
11
- SpanKind,
12
- SpanStatusCode,
13
- } from '@opentelemetry/api';
14
- import type { WorkerTracer } from 'autotel-edge';
15
- import { wrap, setAttr } from './common';
16
-
17
- const pipelineMetaSymbol = Symbol('images-pipeline-meta');
18
-
19
- interface PipelineMeta {
20
- operationCount: number;
21
- }
22
-
23
- interface ImagesLike {
24
- info(blob: ReadableStream | ArrayBuffer | Blob): Promise<{ width: number; height: number; format: string }>;
25
- input(blob: ReadableStream | ArrayBuffer | Blob): ImageTransformerLike;
26
- }
27
-
28
- interface ImageTransformerLike {
29
- transform(options: unknown): ImageTransformerLike;
30
- draw(image: unknown, options?: unknown): ImageTransformerLike;
31
- output(options?: unknown): Promise<ImageOutputLike>;
32
- }
33
-
34
- interface ImageOutputLike {
35
- response(): Response;
36
- blob(): Promise<Blob>;
37
- arrayBuffer(): Promise<ArrayBuffer>;
38
- }
39
-
40
- function proxyTransformer(transformer: ImageTransformerLike, meta: PipelineMeta, bindingName: string): ImageTransformerLike {
41
- const handler: ProxyHandler<ImageTransformerLike> = {
42
- get(target, prop) {
43
- const value = Reflect.get(target, prop);
44
-
45
- if ((prop === 'transform' || prop === 'draw') && typeof value === 'function') {
46
- return new Proxy(value, {
47
- apply: (fnTarget, _thisArg, args) => {
48
- meta.operationCount++;
49
- const result = Reflect.apply(fnTarget, target, args);
50
- // If the result is the transformer itself (fluent chain), return our proxy
51
- if (result === target || (result && typeof result === 'object' && 'output' in result)) {
52
- return proxyTransformer(result as ImageTransformerLike, meta, bindingName);
53
- }
54
- return result;
55
- },
56
- });
57
- }
58
-
59
- if (prop === 'output' && typeof value === 'function') {
60
- return new Proxy(value, {
61
- apply: (fnTarget, _thisArg, args) => {
62
- const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
63
- const [formatOrOptions] = args;
64
-
65
- const attributes: Record<string, string | number> = {
66
- 'images.system': 'cloudflare-images',
67
- 'images.pipeline.operation_count': meta.operationCount,
68
- };
69
-
70
- // Capture output format
71
- if (typeof formatOrOptions === 'string') {
72
- attributes['images.output.format'] = formatOrOptions;
73
- } else if (formatOrOptions && typeof formatOrOptions === 'object') {
74
- const fmt = (formatOrOptions as any).format;
75
- if (fmt) attributes['images.output.format'] = fmt;
76
- }
77
-
78
- return tracer.startActiveSpan(
79
- `Images ${bindingName}: output`,
80
- {
81
- kind: SpanKind.CLIENT,
82
- attributes,
83
- },
84
- async (span) => {
85
- try {
86
- const result = await Reflect.apply(fnTarget, target, args);
87
- span.setStatus({ code: SpanStatusCode.OK });
88
- return result;
89
- } catch (error) {
90
- span.recordException(error as Error);
91
- span.setStatus({
92
- code: SpanStatusCode.ERROR,
93
- message: error instanceof Error ? error.message : String(error),
94
- });
95
- throw error;
96
- } finally {
97
- span.end();
98
- }
99
- },
100
- );
101
- },
102
- });
103
- }
104
-
105
- return value;
106
- },
107
- };
108
-
109
- const proxy = new Proxy(transformer, handler);
110
- Object.defineProperty(proxy, pipelineMetaSymbol, {
111
- value: meta,
112
- writable: false,
113
- enumerable: false,
114
- configurable: false,
115
- });
116
- return proxy;
117
- }
118
-
119
- /**
120
- * Instrument Images binding
121
- */
122
- export function instrumentImages<T extends ImagesLike>(images: T, bindingName?: string): T {
123
- const name = bindingName || 'images';
124
-
125
- const handler: ProxyHandler<T> = {
126
- get(target, prop) {
127
- const value = Reflect.get(target, prop);
128
-
129
- if (prop === 'info' && typeof value === 'function') {
130
- return new Proxy(value, {
131
- apply: (fnTarget, _thisArg, args) => {
132
- const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
133
-
134
- return tracer.startActiveSpan(
135
- `Images ${name}: info`,
136
- {
137
- kind: SpanKind.CLIENT,
138
- attributes: {
139
- 'images.system': 'cloudflare-images',
140
- 'images.operation': 'info',
141
- },
142
- },
143
- async (span) => {
144
- try {
145
- const result = await Reflect.apply(fnTarget, target, args);
146
- setAttr(span, 'images.width', result?.width);
147
- setAttr(span, 'images.height', result?.height);
148
- setAttr(span, 'images.format', result?.format);
149
- span.setStatus({ code: SpanStatusCode.OK });
150
- return result;
151
- } catch (error) {
152
- span.recordException(error as Error);
153
- span.setStatus({
154
- code: SpanStatusCode.ERROR,
155
- message: error instanceof Error ? error.message : String(error),
156
- });
157
- throw error;
158
- } finally {
159
- span.end();
160
- }
161
- },
162
- );
163
- },
164
- });
165
- }
166
-
167
- if (prop === 'input' && typeof value === 'function') {
168
- return new Proxy(value, {
169
- apply: (fnTarget, _thisArg, args) => {
170
- const transformer = Reflect.apply(fnTarget, target, args) as ImageTransformerLike;
171
- const meta: PipelineMeta = { operationCount: 0 };
172
- return proxyTransformer(transformer, meta, name);
173
- },
174
- });
175
- }
176
-
177
- return value;
178
- },
179
- };
180
-
181
- return wrap(images, handler);
182
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * Bindings instrumentation for Cloudflare Workers
3
- * Auto-instrument KV, R2, D1, Service Bindings, and more
4
- */
5
-
6
- export {
7
- instrumentKV,
8
- instrumentR2,
9
- instrumentD1,
10
- instrumentServiceBinding,
11
- instrumentBindings,
12
- } from './bindings';
13
- export { instrumentAI } from './ai';
14
- export { instrumentVectorize } from './vectorize';
15
- export { instrumentHyperdrive } from './hyperdrive';
16
- export { instrumentQueueProducer } from './queue-producer';
17
- export { instrumentAnalyticsEngine } from './analytics-engine';
18
- export { instrumentImages } from './images';
19
- export { instrumentRateLimiter } from './rate-limiter';
20
- export { instrumentBrowserRendering } from './browser-rendering';
@@ -1,224 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { instrumentQueueProducer } from './queue-producer';
3
- import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
4
-
5
- describe('Queue Producer Binding Instrumentation', () => {
6
- let mockTracer: any;
7
- let mockSpan: any;
8
- let getTracerSpy: any;
9
-
10
- beforeEach(() => {
11
- mockSpan = {
12
- spanContext: () => ({
13
- traceId: 'test-trace-id',
14
- spanId: 'test-span-id',
15
- traceFlags: 1,
16
- }),
17
- setAttribute: vi.fn(),
18
- setAttributes: vi.fn(),
19
- setStatus: vi.fn(),
20
- recordException: vi.fn(),
21
- end: vi.fn(),
22
- isRecording: () => true,
23
- updateName: vi.fn(),
24
- addEvent: vi.fn(),
25
- };
26
-
27
- mockTracer = {
28
- startActiveSpan: vi.fn((name, options, fn) => {
29
- return fn(mockSpan);
30
- }),
31
- };
32
-
33
- getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
34
- });
35
-
36
- afterEach(() => {
37
- getTracerSpy.mockRestore();
38
- });
39
-
40
- function createMockQueue(overrides: Partial<Queue> = {}): Queue {
41
- return {
42
- send: vi.fn(async () => ({ messageId: 'msg-123', outcome: 'ok' })),
43
- sendBatch: vi.fn(async () => ({})),
44
- ...overrides,
45
- } as unknown as Queue;
46
- }
47
-
48
- describe('send()', () => {
49
- it('should create span with PRODUCER kind and correct attributes', async () => {
50
- const mockQueue = createMockQueue();
51
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
52
-
53
- await instrumented.send({ data: 'test-payload' });
54
-
55
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
56
-
57
- const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
58
- expect(spanName).toBe('Queue my-queue: send');
59
- expect(options.kind).toBe(SpanKind.PRODUCER);
60
- expect(options.attributes['messaging.system']).toBe('cloudflare-queues');
61
- expect(options.attributes['messaging.operation.type']).toBe('publish');
62
- expect(options.attributes['messaging.operation']).toBe('send');
63
- expect(options.attributes['messaging.destination.name']).toBe('my-queue');
64
- });
65
-
66
- it('should record messageId from result when available', async () => {
67
- const mockQueue = createMockQueue({
68
- send: vi.fn(async () => ({ messageId: 'msg-456', outcome: 'ok' })) as any,
69
- });
70
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
71
-
72
- await instrumented.send({ data: 'test-payload' });
73
-
74
- expect(mockSpan.setAttribute).toHaveBeenCalledWith('messaging.message.id', 'msg-456');
75
- expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
76
- expect(mockSpan.end).toHaveBeenCalled();
77
- });
78
-
79
- it('should not set messageId attribute when result has no messageId', async () => {
80
- const mockQueue = createMockQueue({
81
- send: vi.fn(async () => ({})) as any,
82
- });
83
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
84
-
85
- await instrumented.send({ data: 'test-payload' });
86
-
87
- // setAttr uses the common helper which skips undefined/null values
88
- const messageIdCalls = mockSpan.setAttribute.mock.calls.filter(
89
- (call: any) => call[0] === 'messaging.message.id'
90
- );
91
- expect(messageIdCalls.length).toBe(0);
92
- });
93
-
94
- it('should handle errors in send()', async () => {
95
- const sendError = new Error('Queue full');
96
- const mockQueue = createMockQueue({
97
- send: vi.fn(async () => {
98
- throw sendError;
99
- }) as any,
100
- });
101
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
102
-
103
- await expect(instrumented.send({ data: 'test-payload' })).rejects.toThrow('Queue full');
104
-
105
- expect(mockSpan.recordException).toHaveBeenCalledWith(sendError);
106
- expect(mockSpan.setStatus).toHaveBeenCalledWith({
107
- code: SpanStatusCode.ERROR,
108
- message: 'Queue full',
109
- });
110
- expect(mockSpan.end).toHaveBeenCalled();
111
- });
112
-
113
- it('should use default queue name when none provided', async () => {
114
- const mockQueue = createMockQueue();
115
- const instrumented = instrumentQueueProducer(mockQueue);
116
-
117
- await instrumented.send({ data: 'test-payload' });
118
-
119
- const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
120
- expect(spanName).toBe('Queue queue: send');
121
- expect(options.attributes['messaging.destination.name']).toBe('queue');
122
- });
123
- });
124
-
125
- describe('sendBatch()', () => {
126
- it('should create span with batch_message_count', async () => {
127
- const mockQueue = createMockQueue();
128
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
129
-
130
- const messages = [
131
- { body: { data: 'msg-1' } },
132
- { body: { data: 'msg-2' } },
133
- { body: { data: 'msg-3' } },
134
- ];
135
-
136
- await instrumented.sendBatch(messages);
137
-
138
- expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
139
-
140
- const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
141
- expect(spanName).toBe('Queue my-queue: sendBatch');
142
- expect(options.kind).toBe(SpanKind.PRODUCER);
143
- expect(options.attributes['messaging.system']).toBe('cloudflare-queues');
144
- expect(options.attributes['messaging.operation.type']).toBe('publish');
145
- expect(options.attributes['messaging.operation']).toBe('sendBatch');
146
- expect(options.attributes['messaging.destination.name']).toBe('my-queue');
147
- expect(options.attributes['messaging.batch.message_count']).toBe(3);
148
- });
149
-
150
- it('should set OK status and end span on success', async () => {
151
- const mockQueue = createMockQueue();
152
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
153
-
154
- await instrumented.sendBatch([{ body: { data: 'msg-1' } }]);
155
-
156
- expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
157
- expect(mockSpan.end).toHaveBeenCalled();
158
- });
159
-
160
- it('should handle errors in sendBatch()', async () => {
161
- const batchError = new Error('Batch limit exceeded');
162
- const mockQueue = createMockQueue({
163
- sendBatch: vi.fn(async () => {
164
- throw batchError;
165
- }) as any,
166
- });
167
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
168
-
169
- const messages = [{ body: { data: 'msg-1' } }, { body: { data: 'msg-2' } }];
170
-
171
- await expect(instrumented.sendBatch(messages)).rejects.toThrow('Batch limit exceeded');
172
-
173
- expect(mockSpan.recordException).toHaveBeenCalledWith(batchError);
174
- expect(mockSpan.setStatus).toHaveBeenCalledWith({
175
- code: SpanStatusCode.ERROR,
176
- message: 'Batch limit exceeded',
177
- });
178
- expect(mockSpan.end).toHaveBeenCalled();
179
- });
180
- });
181
-
182
- describe('this-binding', () => {
183
- it('should invoke send() with original object as this, not the proxy', async () => {
184
- let receivedThis: any;
185
- const mockQueue = {
186
- send: vi.fn(async function(this: any) {
187
- // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
188
- receivedThis = this;
189
- return { messageId: 'msg-123' };
190
- }),
191
- sendBatch: vi.fn(async () => ({})),
192
- } as unknown as Queue;
193
- const instrumented = instrumentQueueProducer(mockQueue, 'test');
194
- await instrumented.send({ data: 'test' });
195
- expect(receivedThis).toBe(mockQueue);
196
- });
197
-
198
- it('should invoke sendBatch() with original object as this, not the proxy', async () => {
199
- let receivedThis: any;
200
- const mockQueue = {
201
- send: vi.fn(async () => ({ messageId: 'msg-123' })),
202
- sendBatch: vi.fn(async function(this: any) {
203
- // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
204
- receivedThis = this;
205
- return {};
206
- }),
207
- } as unknown as Queue;
208
- const instrumented = instrumentQueueProducer(mockQueue, 'test');
209
- await instrumented.sendBatch([{ body: { data: 'test' } }]);
210
- expect(receivedThis).toBe(mockQueue);
211
- });
212
- });
213
-
214
- describe('non-instrumented methods', () => {
215
- it('should pass through non-instrumented properties', () => {
216
- const mockQueue = createMockQueue();
217
- (mockQueue as any).customProp = 'test-value';
218
-
219
- const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
220
-
221
- expect((instrumented as any).customProp).toBe('test-value');
222
- });
223
- });
224
- });