autotel-cloudflare 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +432 -0
  3. package/dist/actors.d.ts +248 -0
  4. package/dist/actors.js +1030 -0
  5. package/dist/actors.js.map +1 -0
  6. package/dist/agents.d.ts +219 -0
  7. package/dist/agents.js +276 -0
  8. package/dist/agents.js.map +1 -0
  9. package/dist/bindings.d.ts +40 -0
  10. package/dist/bindings.js +4 -0
  11. package/dist/bindings.js.map +1 -0
  12. package/dist/chunk-JDPN3HND.js +520 -0
  13. package/dist/chunk-JDPN3HND.js.map +1 -0
  14. package/dist/chunk-QXFYTHQF.js +298 -0
  15. package/dist/chunk-QXFYTHQF.js.map +1 -0
  16. package/dist/chunk-SKKRPS5K.js +50 -0
  17. package/dist/chunk-SKKRPS5K.js.map +1 -0
  18. package/dist/events.d.ts +1 -0
  19. package/dist/events.js +3 -0
  20. package/dist/events.js.map +1 -0
  21. package/dist/handlers.d.ts +121 -0
  22. package/dist/handlers.js +4 -0
  23. package/dist/handlers.js.map +1 -0
  24. package/dist/index.d.ts +144 -0
  25. package/dist/index.js +576 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/logger.d.ts +1 -0
  28. package/dist/logger.js +3 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/sampling.d.ts +4 -0
  31. package/dist/sampling.js +3 -0
  32. package/dist/sampling.js.map +1 -0
  33. package/dist/testing.d.ts +1 -0
  34. package/dist/testing.js +3 -0
  35. package/dist/testing.js.map +1 -0
  36. package/package.json +107 -0
  37. package/src/actors/alarms.ts +225 -0
  38. package/src/actors/index.ts +36 -0
  39. package/src/actors/instrument-actor.test.ts +179 -0
  40. package/src/actors/instrument-actor.ts +574 -0
  41. package/src/actors/sockets.ts +217 -0
  42. package/src/actors/storage.ts +263 -0
  43. package/src/actors/traced-handler.ts +300 -0
  44. package/src/actors/types.ts +98 -0
  45. package/src/actors.ts +50 -0
  46. package/src/agents/index.ts +42 -0
  47. package/src/agents/otel-observability.test.ts +329 -0
  48. package/src/agents/otel-observability.ts +465 -0
  49. package/src/agents/types.ts +167 -0
  50. package/src/agents.ts +76 -0
  51. package/src/bindings/bindings.ts +621 -0
  52. package/src/bindings/common.ts +75 -0
  53. package/src/bindings/index.ts +12 -0
  54. package/src/bindings.ts +6 -0
  55. package/src/events.ts +6 -0
  56. package/src/global/cache.test.ts +292 -0
  57. package/src/global/cache.ts +164 -0
  58. package/src/global/fetch.test.ts +344 -0
  59. package/src/global/fetch.ts +134 -0
  60. package/src/global/index.ts +7 -0
  61. package/src/handlers/durable-objects.test.ts +524 -0
  62. package/src/handlers/durable-objects.ts +250 -0
  63. package/src/handlers/index.ts +6 -0
  64. package/src/handlers/workflows.ts +318 -0
  65. package/src/handlers.ts +6 -0
  66. package/src/index.ts +57 -0
  67. package/src/logger.ts +6 -0
  68. package/src/sampling.ts +6 -0
  69. package/src/testing.ts +6 -0
  70. package/src/wrappers/index.ts +8 -0
  71. package/src/wrappers/instrument.integration.test.ts +468 -0
  72. package/src/wrappers/instrument.ts +643 -0
  73. package/src/wrappers/wrap-do.ts +34 -0
  74. package/src/wrappers/wrap-module.ts +37 -0
@@ -0,0 +1,468 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { instrument } from './instrument';
3
+ import { trace } from 'autotel-edge';
4
+ import type { ExportedHandler } from '@cloudflare/workers-types';
5
+
6
+ describe('Handler Instrumentation - Integration Tests', () => {
7
+ interface Env {
8
+ OTLP_ENDPOINT?: string;
9
+ API_KEY?: string;
10
+ }
11
+
12
+ describe('instrument() for Fetch Handlers', () => {
13
+ it('should instrument a basic fetch handler', async () => {
14
+ const handler: ExportedHandler<Env> = {
15
+ async fetch(request, env, ctx) {
16
+ return new Response('Hello World', { status: 200 });
17
+ },
18
+ };
19
+
20
+ const instrumented = instrument(handler, (env: Env) => ({
21
+ service: {
22
+ name: 'test-worker',
23
+ version: '1.0.0',
24
+ },
25
+ exporter: {
26
+ url: env.OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
27
+ headers: { 'x-api-key': env.API_KEY || 'test-key' },
28
+ },
29
+ }));
30
+
31
+ expect(instrumented).toBeDefined();
32
+ expect(typeof instrumented.fetch).toBe('function');
33
+ });
34
+
35
+ it('should create a new handler that wraps the original', async () => {
36
+ let handlerCalled = false;
37
+
38
+ const handler: ExportedHandler<Env> = {
39
+ async fetch(request, env, ctx) {
40
+ handlerCalled = true;
41
+ return new Response('Hello World', { status: 200 });
42
+ },
43
+ };
44
+
45
+ const instrumented = instrument(handler, {
46
+ service: { name: 'test-worker' },
47
+ });
48
+
49
+ const request = new Request('http://example.com/test');
50
+ const env = {} as Env;
51
+ const ctx = {
52
+ waitUntil: vi.fn(),
53
+ passThroughOnException: vi.fn(),
54
+ } as any;
55
+
56
+ await instrumented.fetch(request, env, ctx);
57
+
58
+ expect(handlerCalled).toBe(true);
59
+ });
60
+
61
+ it('should call waitUntil to flush spans', async () => {
62
+ const handler: ExportedHandler<Env> = {
63
+ async fetch(request, env, ctx) {
64
+ return new Response('Hello World', { status: 200 });
65
+ },
66
+ };
67
+
68
+ const instrumented = instrument(handler, {
69
+ service: { name: 'test-worker' },
70
+ });
71
+
72
+ const request = new Request('http://example.com/test');
73
+ const env = {} as Env;
74
+ const waitUntilSpy = vi.fn();
75
+ const ctx = {
76
+ waitUntil: waitUntilSpy,
77
+ passThroughOnException: vi.fn(),
78
+ } as any;
79
+
80
+ await instrumented.fetch(request, env, ctx);
81
+
82
+ // waitUntil should be called to flush spans
83
+ expect(waitUntilSpy).toHaveBeenCalled();
84
+ });
85
+
86
+ it('should preserve handler errors', async () => {
87
+ const handler: ExportedHandler<Env> = {
88
+ async fetch(request, env, ctx) {
89
+ throw new Error('Handler error');
90
+ },
91
+ };
92
+
93
+ const instrumented = instrument(handler, {
94
+ service: { name: 'test-worker' },
95
+ });
96
+
97
+ const request = new Request('http://example.com/test');
98
+ const env = {} as Env;
99
+ const ctx = {
100
+ waitUntil: vi.fn(),
101
+ passThroughOnException: vi.fn(),
102
+ } as any;
103
+
104
+ await expect(instrumented.fetch(request, env, ctx)).rejects.toThrow('Handler error');
105
+ });
106
+
107
+ it('should work with trace() functions inside handler', async () => {
108
+ const createUser = trace(async function createUser(email: string) {
109
+ return { id: '123', email };
110
+ });
111
+
112
+ const handler: ExportedHandler<Env> = {
113
+ async fetch(request, env, ctx) {
114
+ const user = await createUser('test@example.com');
115
+ return Response.json(user);
116
+ },
117
+ };
118
+
119
+ const instrumented = instrument(handler, {
120
+ service: { name: 'test-worker' },
121
+ });
122
+
123
+ const request = new Request('http://example.com/users');
124
+ const env = {} as Env;
125
+ const ctx = {
126
+ waitUntil: vi.fn(),
127
+ passThroughOnException: vi.fn(),
128
+ } as any;
129
+
130
+ const response = await instrumented.fetch(request, env, ctx);
131
+ const data = await response.json();
132
+
133
+ expect(data).toEqual({ id: '123', email: 'test@example.com' });
134
+ });
135
+
136
+ it('should accept static config object', async () => {
137
+ const handler: ExportedHandler<Env> = {
138
+ async fetch(request, env, ctx) {
139
+ return new Response('Hello', { status: 200 });
140
+ },
141
+ };
142
+
143
+ const instrumented = instrument(handler, {
144
+ service: {
145
+ name: 'test-worker',
146
+ version: '1.0.0',
147
+ namespace: 'testing',
148
+ },
149
+ exporter: {
150
+ url: 'http://localhost:4318/v1/traces',
151
+ },
152
+ });
153
+
154
+ expect(instrumented).toBeDefined();
155
+ expect(typeof instrumented.fetch).toBe('function');
156
+ });
157
+
158
+ it('should accept config function', async () => {
159
+ const handler: ExportedHandler<Env> = {
160
+ async fetch(request, env, ctx) {
161
+ return new Response('Hello', { status: 200 });
162
+ },
163
+ };
164
+
165
+ const instrumented = instrument(handler, (env: Env) => ({
166
+ service: { name: env.OTLP_ENDPOINT ? 'prod-worker' : 'dev-worker' },
167
+ }));
168
+
169
+ expect(instrumented).toBeDefined();
170
+ expect(typeof instrumented.fetch).toBe('function');
171
+ });
172
+ });
173
+
174
+ describe('Handler with Scheduled Events', () => {
175
+ it('should instrument scheduled handler', async () => {
176
+ let scheduledCalled = false;
177
+
178
+ const handler: ExportedHandler<Env> = {
179
+ async fetch(request, env, ctx) {
180
+ return new Response('Hello', { status: 200 });
181
+ },
182
+ async scheduled(event, env, ctx) {
183
+ scheduledCalled = true;
184
+ },
185
+ };
186
+
187
+ const instrumented = instrument(handler, {
188
+ service: { name: 'test-worker' },
189
+ });
190
+
191
+ expect(instrumented.scheduled).toBeDefined();
192
+
193
+ if (instrumented.scheduled) {
194
+ const event = { scheduledTime: Date.now(), cron: '0 0 * * *' } as ScheduledController;
195
+ const env = {} as Env;
196
+ const ctx = {
197
+ waitUntil: vi.fn(),
198
+ passThroughOnException: vi.fn(),
199
+ } as any;
200
+
201
+ await instrumented.scheduled(event, env, ctx);
202
+
203
+ expect(scheduledCalled).toBe(true);
204
+ expect(ctx.waitUntil).toHaveBeenCalled();
205
+ }
206
+ });
207
+ });
208
+
209
+ describe('Handler with Queue Events', () => {
210
+ it('should instrument queue handler', async () => {
211
+ let queueCalled = false;
212
+ let messageCount = 0;
213
+
214
+ const handler: ExportedHandler<Env> = {
215
+ async queue(batch, env, ctx) {
216
+ queueCalled = true;
217
+ messageCount = batch.messages.length;
218
+ },
219
+ };
220
+
221
+ const instrumented = instrument(handler, {
222
+ service: { name: 'test-worker' },
223
+ });
224
+
225
+ expect(instrumented.queue).toBeDefined();
226
+
227
+ if (instrumented.queue) {
228
+ const batch = {
229
+ messages: [
230
+ { id: '1', body: 'message1', timestamp: new Date() },
231
+ { id: '2', body: 'message2', timestamp: new Date() },
232
+ ],
233
+ queue: 'test-queue',
234
+ ackAll: vi.fn(),
235
+ retryAll: vi.fn(),
236
+ } as MessageBatch;
237
+ const env = {} as Env;
238
+ const ctx = {
239
+ waitUntil: vi.fn(),
240
+ passThroughOnException: vi.fn(),
241
+ } as any;
242
+
243
+ await instrumented.queue(batch, env, ctx);
244
+
245
+ expect(queueCalled).toBe(true);
246
+ expect(messageCount).toBe(2);
247
+ expect(ctx.waitUntil).toHaveBeenCalled();
248
+ }
249
+ });
250
+
251
+ it('should track message ack operations', async () => {
252
+ const handler: ExportedHandler<Env> = {
253
+ async queue(batch, env, ctx) {
254
+ // Ack individual messages
255
+ batch.messages[0].ack();
256
+ batch.messages[1].ack();
257
+ },
258
+ };
259
+
260
+ const instrumented = instrument(handler, {
261
+ service: { name: 'test-worker' },
262
+ });
263
+
264
+ if (instrumented.queue) {
265
+ const messages = [
266
+ { id: '1', body: 'message1', timestamp: new Date(), ack: vi.fn(), retry: vi.fn() },
267
+ { id: '2', body: 'message2', timestamp: new Date(), ack: vi.fn(), retry: vi.fn() },
268
+ ];
269
+ const batch = {
270
+ messages,
271
+ queue: 'test-queue',
272
+ ackAll: vi.fn(),
273
+ retryAll: vi.fn(),
274
+ } as MessageBatch;
275
+ const env = {} as Env;
276
+ const ctx = {
277
+ waitUntil: vi.fn(),
278
+ passThroughOnException: vi.fn(),
279
+ } as any;
280
+
281
+ await instrumented.queue(batch, env, ctx);
282
+
283
+ // Verify ack was called on both messages
284
+ expect(messages[0].ack).toHaveBeenCalled();
285
+ expect(messages[1].ack).toHaveBeenCalled();
286
+ }
287
+ });
288
+
289
+ it('should track message retry operations', async () => {
290
+ const handler: ExportedHandler<Env> = {
291
+ async queue(batch, env, ctx) {
292
+ // Retry a message
293
+ batch.messages[0].retry();
294
+ },
295
+ };
296
+
297
+ const instrumented = instrument(handler, {
298
+ service: { name: 'test-worker' },
299
+ });
300
+
301
+ if (instrumented.queue) {
302
+ const messages = [
303
+ { id: '1', body: 'message1', timestamp: new Date(), ack: vi.fn(), retry: vi.fn() },
304
+ ];
305
+ const batch = {
306
+ messages,
307
+ queue: 'test-queue',
308
+ ackAll: vi.fn(),
309
+ retryAll: vi.fn(),
310
+ } as MessageBatch;
311
+ const env = {} as Env;
312
+ const ctx = {
313
+ waitUntil: vi.fn(),
314
+ passThroughOnException: vi.fn(),
315
+ } as any;
316
+
317
+ await instrumented.queue(batch, env, ctx);
318
+
319
+ // Verify retry was called
320
+ expect(messages[0].retry).toHaveBeenCalled();
321
+ }
322
+ });
323
+
324
+ it('should track ackAll operation', async () => {
325
+ const handler: ExportedHandler<Env> = {
326
+ async queue(batch, env, ctx) {
327
+ batch.ackAll();
328
+ },
329
+ };
330
+
331
+ const instrumented = instrument(handler, {
332
+ service: { name: 'test-worker' },
333
+ });
334
+
335
+ if (instrumented.queue) {
336
+ const ackAllFn = vi.fn();
337
+ const batch = {
338
+ messages: [
339
+ { id: '1', body: 'message1', timestamp: new Date() },
340
+ { id: '2', body: 'message2', timestamp: new Date() },
341
+ ],
342
+ queue: 'test-queue',
343
+ ackAll: ackAllFn,
344
+ retryAll: vi.fn(),
345
+ } as MessageBatch;
346
+ const env = {} as Env;
347
+ const ctx = {
348
+ waitUntil: vi.fn(),
349
+ passThroughOnException: vi.fn(),
350
+ } as any;
351
+
352
+ await instrumented.queue(batch, env, ctx);
353
+
354
+ expect(ackAllFn).toHaveBeenCalled();
355
+ }
356
+ });
357
+ });
358
+
359
+ describe('Handler with Email Events', () => {
360
+ it('should instrument email handler', async () => {
361
+ let emailCalled = false;
362
+
363
+ const handler: ExportedHandler<Env> = {
364
+ async email(message, env, ctx) {
365
+ emailCalled = true;
366
+ },
367
+ };
368
+
369
+ const instrumented = instrument(handler, {
370
+ service: { name: 'test-worker' },
371
+ });
372
+
373
+ expect(instrumented.email).toBeDefined();
374
+
375
+ if (instrumented.email) {
376
+ const message = {
377
+ from: 'sender@example.com',
378
+ to: 'recipient@example.com',
379
+ headers: new Headers({ subject: 'Test Email' }),
380
+ } as ForwardableEmailMessage;
381
+ const env = {} as Env;
382
+ const ctx = {
383
+ waitUntil: vi.fn(),
384
+ passThroughOnException: vi.fn(),
385
+ } as any;
386
+
387
+ await instrumented.email(message, env, ctx);
388
+
389
+ expect(emailCalled).toBe(true);
390
+ expect(ctx.waitUntil).toHaveBeenCalled();
391
+ }
392
+ });
393
+ });
394
+
395
+ describe('Context Propagation', () => {
396
+ it('should propagate trace context from request headers', async () => {
397
+ let receivedContext = false;
398
+
399
+ const handler: ExportedHandler<Env> = {
400
+ async fetch(request, env, ctx) {
401
+ // If context is propagated, we should have trace info
402
+ const traceparent = request.headers.get('traceparent');
403
+ receivedContext = !!traceparent;
404
+ return new Response('OK', { status: 200 });
405
+ },
406
+ };
407
+
408
+ const instrumented = instrument(handler, {
409
+ service: { name: 'test-worker' },
410
+ });
411
+
412
+ const request = new Request('http://example.com/test', {
413
+ headers: {
414
+ 'traceparent': '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
415
+ },
416
+ });
417
+ const env = {} as Env;
418
+ const ctx = {
419
+ waitUntil: vi.fn(),
420
+ passThroughOnException: vi.fn(),
421
+ } as any;
422
+
423
+ await instrumented.fetch(request, env, ctx);
424
+
425
+ expect(receivedContext).toBe(true);
426
+ });
427
+ });
428
+
429
+ describe('Multiple Handlers', () => {
430
+ it('should instrument multiple handlers independently', async () => {
431
+ const handler1: ExportedHandler<Env> = {
432
+ async fetch(request, env, ctx) {
433
+ return new Response('Handler 1', { status: 200 });
434
+ },
435
+ };
436
+
437
+ const handler2: ExportedHandler<Env> = {
438
+ async fetch(request, env, ctx) {
439
+ return new Response('Handler 2', { status: 200 });
440
+ },
441
+ };
442
+
443
+ const instrumented1 = instrument(handler1, {
444
+ service: { name: 'worker-1' },
445
+ });
446
+
447
+ const instrumented2 = instrument(handler2, {
448
+ service: { name: 'worker-2' },
449
+ });
450
+
451
+ const request = new Request('http://example.com/test');
452
+ const env = {} as Env;
453
+ const ctx = {
454
+ waitUntil: vi.fn(),
455
+ passThroughOnException: vi.fn(),
456
+ } as any;
457
+
458
+ const response1 = await instrumented1.fetch(request, env, ctx);
459
+ const response2 = await instrumented2.fetch(request, env, ctx);
460
+
461
+ const text1 = await response1.text();
462
+ const text2 = await response2.text();
463
+
464
+ expect(text1).toBe('Handler 1');
465
+ expect(text2).toBe('Handler 2');
466
+ });
467
+ });
468
+ });