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,524 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { instrumentDO } from './durable-objects';
3
+ import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
4
+ import type { DurableObjectState } from '@cloudflare/workers-types';
5
+
6
+ describe('Durable Objects Instrumentation', () => {
7
+ let mockTracer: any;
8
+ let mockSpan: any;
9
+ let getTracerSpy: any;
10
+
11
+ beforeEach(() => {
12
+ mockSpan = {
13
+ spanContext: () => ({
14
+ traceId: 'test-trace-id',
15
+ spanId: 'test-span-id',
16
+ traceFlags: 1,
17
+ }),
18
+ setAttribute: vi.fn(),
19
+ setAttributes: vi.fn(),
20
+ setStatus: vi.fn(),
21
+ recordException: vi.fn(),
22
+ end: vi.fn(),
23
+ isRecording: () => true,
24
+ updateName: vi.fn(),
25
+ addEvent: vi.fn(),
26
+ };
27
+
28
+ mockTracer = {
29
+ startActiveSpan: vi.fn((name, options, context, fn) => {
30
+ if (typeof options === 'function') {
31
+ return options(mockSpan);
32
+ }
33
+ if (typeof context === 'function') {
34
+ return context(mockSpan);
35
+ }
36
+ if (typeof fn === 'function') {
37
+ return fn(mockSpan);
38
+ }
39
+ return Promise.resolve();
40
+ }),
41
+ };
42
+
43
+ getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
44
+ });
45
+
46
+ afterEach(() => {
47
+ getTracerSpy.mockRestore();
48
+ });
49
+
50
+ describe('instrumentDO()', () => {
51
+ it('should wrap DO class constructor', () => {
52
+ class TestDO {
53
+ constructor(public state: DurableObjectState, public env: any) {}
54
+
55
+ async fetch(request: Request) {
56
+ return new Response('Hello');
57
+ }
58
+ }
59
+
60
+ const InstrumentedDO = instrumentDO(TestDO, {
61
+ service: { name: 'test-do' },
62
+ });
63
+
64
+ expect(InstrumentedDO).toBeDefined();
65
+ expect(typeof InstrumentedDO).toBe('function');
66
+ });
67
+
68
+ it('should create DO instance with instrumentation', () => {
69
+ class TestDO {
70
+ constructor(public state: DurableObjectState, public env: any) {}
71
+
72
+ async fetch(request: Request) {
73
+ return new Response('Hello');
74
+ }
75
+ }
76
+
77
+ const InstrumentedDO = instrumentDO(TestDO, {
78
+ service: { name: 'test-do' },
79
+ });
80
+
81
+ const mockState = {
82
+ id: {
83
+ toString: () => 'test-id-123',
84
+ name: 'test-do-name',
85
+ equals: () => false,
86
+ },
87
+ storage: {},
88
+ blockConcurrencyWhile: vi.fn(),
89
+ waitUntil: vi.fn(),
90
+ } as unknown as DurableObjectState;
91
+
92
+ const mockEnv = {};
93
+
94
+ const instance = new InstrumentedDO(mockState, mockEnv);
95
+
96
+ expect(instance).toBeDefined();
97
+ expect(typeof instance.fetch).toBe('function');
98
+ });
99
+
100
+ it('should accept static config', () => {
101
+ class TestDO {
102
+ constructor(public state: DurableObjectState, public env: any) {}
103
+
104
+ async fetch(request: Request) {
105
+ return new Response('Hello');
106
+ }
107
+ }
108
+
109
+ const InstrumentedDO = instrumentDO(TestDO, {
110
+ service: { name: 'test-do', version: '1.0.0' },
111
+ exporter: {
112
+ url: 'http://localhost:4318/v1/traces',
113
+ },
114
+ });
115
+
116
+ expect(InstrumentedDO).toBeDefined();
117
+ });
118
+
119
+ it('should accept config function', () => {
120
+ class TestDO {
121
+ constructor(public state: DurableObjectState, public env: any) {}
122
+
123
+ async fetch(request: Request) {
124
+ return new Response('Hello');
125
+ }
126
+ }
127
+
128
+ interface Env {
129
+ OTLP_ENDPOINT: string;
130
+ }
131
+
132
+ const InstrumentedDO = instrumentDO(TestDO, (env: Env) => ({
133
+ service: { name: 'test-do' },
134
+ exporter: {
135
+ url: env.OTLP_ENDPOINT,
136
+ },
137
+ }));
138
+
139
+ expect(InstrumentedDO).toBeDefined();
140
+ });
141
+ });
142
+
143
+ describe('fetch() instrumentation', () => {
144
+ it('should create span for fetch() calls', async () => {
145
+ class TestDO {
146
+ constructor(public state: DurableObjectState, public env: any) {}
147
+
148
+ async fetch(request: Request) {
149
+ return new Response('Hello from DO');
150
+ }
151
+ }
152
+
153
+ const InstrumentedDO = instrumentDO(TestDO, {
154
+ service: { name: 'test-do' },
155
+ });
156
+
157
+ const mockState = {
158
+ id: {
159
+ toString: () => 'test-id-123',
160
+ name: 'test-do-name',
161
+ equals: () => false,
162
+ },
163
+ storage: {},
164
+ blockConcurrencyWhile: vi.fn(),
165
+ waitUntil: vi.fn(),
166
+ } as unknown as DurableObjectState;
167
+
168
+ const instance = new InstrumentedDO(mockState, {});
169
+ const request = new Request('http://example.com/test');
170
+ const response = await instance.fetch(request);
171
+
172
+ expect(mockTracer.startActiveSpan).toHaveBeenCalled();
173
+
174
+ const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
175
+ expect(spanName).toContain('DO');
176
+ expect(spanName).toContain('test-do-name');
177
+ expect(spanName).toContain('GET');
178
+
179
+ const text = await response.text();
180
+ expect(text).toBe('Hello from DO');
181
+ });
182
+
183
+ it('should add HTTP attributes (method, URL, status)', async () => {
184
+ class TestDO {
185
+ constructor(public state: DurableObjectState, public env: any) {}
186
+
187
+ async fetch(request: Request) {
188
+ return new Response('OK', { status: 200 });
189
+ }
190
+ }
191
+
192
+ const InstrumentedDO = instrumentDO(TestDO, {
193
+ service: { name: 'test-do' },
194
+ });
195
+
196
+ const mockState = {
197
+ id: {
198
+ toString: () => 'test-id-123',
199
+ name: 'counter',
200
+ equals: () => false,
201
+ },
202
+ storage: {},
203
+ blockConcurrencyWhile: vi.fn(),
204
+ waitUntil: vi.fn(),
205
+ } as unknown as DurableObjectState;
206
+
207
+ const instance = new InstrumentedDO(mockState, {});
208
+ const request = new Request('http://example.com/increment', { method: 'POST' });
209
+ await instance.fetch(request);
210
+
211
+ const options = mockTracer.startActiveSpan.mock.calls[0][1];
212
+ expect(options.kind).toBe(SpanKind.SERVER);
213
+ expect(options.attributes['http.request.method']).toBe('POST');
214
+ expect(options.attributes['url.full']).toBe('http://example.com/increment');
215
+ });
216
+
217
+ it('should add DO-specific attributes (do.id, do.id.name)', async () => {
218
+ class TestDO {
219
+ constructor(public state: DurableObjectState, public env: any) {}
220
+
221
+ async fetch(request: Request) {
222
+ return new Response('OK');
223
+ }
224
+ }
225
+
226
+ const InstrumentedDO = instrumentDO(TestDO, {
227
+ service: { name: 'test-do' },
228
+ });
229
+
230
+ const mockState = {
231
+ id: {
232
+ toString: () => 'unique-do-id-456',
233
+ name: 'my-counter',
234
+ equals: () => false,
235
+ },
236
+ storage: {},
237
+ blockConcurrencyWhile: vi.fn(),
238
+ waitUntil: vi.fn(),
239
+ } as unknown as DurableObjectState;
240
+
241
+ const instance = new InstrumentedDO(mockState, {});
242
+ const request = new Request('http://example.com/test');
243
+ await instance.fetch(request);
244
+
245
+ const options = mockTracer.startActiveSpan.mock.calls[0][1];
246
+ expect(options.attributes['do.id']).toBe('unique-do-id-456');
247
+ expect(options.attributes['do.id.name']).toBe('my-counter');
248
+ expect(options.attributes['faas.trigger']).toBe('http');
249
+ });
250
+
251
+ it('should track cold starts (first call = true, subsequent = false)', async () => {
252
+ class TestDO {
253
+ constructor(public state: DurableObjectState, public env: any) {}
254
+
255
+ async fetch(request: Request) {
256
+ return new Response('OK');
257
+ }
258
+ }
259
+
260
+ const InstrumentedDO = instrumentDO(TestDO, {
261
+ service: { name: 'test-do' },
262
+ });
263
+
264
+ const mockState = {
265
+ id: {
266
+ toString: () => 'test-id',
267
+ name: 'test',
268
+ equals: () => false,
269
+ },
270
+ storage: {},
271
+ blockConcurrencyWhile: vi.fn(),
272
+ waitUntil: vi.fn(),
273
+ } as unknown as DurableObjectState;
274
+
275
+ const instance = new InstrumentedDO(mockState, {});
276
+ const request = new Request('http://example.com/test');
277
+
278
+ // First call - should be cold start
279
+ await instance.fetch(request);
280
+ const firstCallOptions = mockTracer.startActiveSpan.mock.calls[0][1];
281
+ expect(firstCallOptions.attributes['faas.coldstart']).toBe(true);
282
+
283
+ // Second call - should NOT be cold start
284
+ await instance.fetch(request);
285
+ const secondCallOptions = mockTracer.startActiveSpan.mock.calls[1][1];
286
+ expect(secondCallOptions.attributes['faas.coldstart']).toBe(false);
287
+ });
288
+
289
+ it('should handle fetch() errors', async () => {
290
+ class TestDO {
291
+ constructor(public state: DurableObjectState, public env: any) {}
292
+
293
+ async fetch(request: Request) {
294
+ throw new Error('DO error');
295
+ }
296
+ }
297
+
298
+ const InstrumentedDO = instrumentDO(TestDO, {
299
+ service: { name: 'test-do' },
300
+ });
301
+
302
+ const mockState = {
303
+ id: {
304
+ toString: () => 'test-id',
305
+ name: 'test',
306
+ equals: () => false,
307
+ },
308
+ storage: {},
309
+ blockConcurrencyWhile: vi.fn(),
310
+ waitUntil: vi.fn(),
311
+ } as unknown as DurableObjectState;
312
+
313
+ const instance = new InstrumentedDO(mockState, {});
314
+ const request = new Request('http://example.com/test');
315
+
316
+ await expect(instance.fetch(request)).rejects.toThrow('DO error');
317
+
318
+ expect(mockSpan.recordException).toHaveBeenCalled();
319
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
320
+ code: SpanStatusCode.ERROR,
321
+ message: 'DO error',
322
+ });
323
+ expect(mockSpan.end).toHaveBeenCalled();
324
+ });
325
+
326
+ it('should preserve "this" binding', async () => {
327
+ class TestDO {
328
+ private counter = 0;
329
+
330
+ constructor(public state: DurableObjectState, public env: any) {}
331
+
332
+ async fetch(request: Request) {
333
+ this.counter++;
334
+ return new Response(`Count: ${this.counter}`);
335
+ }
336
+ }
337
+
338
+ const InstrumentedDO = instrumentDO(TestDO, {
339
+ service: { name: 'test-do' },
340
+ });
341
+
342
+ const mockState = {
343
+ id: {
344
+ toString: () => 'test-id',
345
+ name: 'test',
346
+ equals: () => false,
347
+ },
348
+ storage: {},
349
+ blockConcurrencyWhile: vi.fn(),
350
+ waitUntil: vi.fn(),
351
+ } as unknown as DurableObjectState;
352
+
353
+ const instance = new InstrumentedDO(mockState, {});
354
+
355
+ const response1 = await instance.fetch(new Request('http://example.com/test'));
356
+ const text1 = await response1.text();
357
+ expect(text1).toBe('Count: 1');
358
+
359
+ const response2 = await instance.fetch(new Request('http://example.com/test'));
360
+ const text2 = await response2.text();
361
+ expect(text2).toBe('Count: 2');
362
+ });
363
+ });
364
+
365
+ describe('alarm() instrumentation', () => {
366
+ it('should create span for alarm() calls', async () => {
367
+ class TestDO {
368
+ constructor(public state: DurableObjectState, public env: any) {}
369
+
370
+ async fetch(request: Request) {
371
+ return new Response('OK');
372
+ }
373
+
374
+ async alarm() {
375
+ // Alarm logic
376
+ }
377
+ }
378
+
379
+ const InstrumentedDO = instrumentDO(TestDO, {
380
+ service: { name: 'test-do' },
381
+ });
382
+
383
+ const mockState = {
384
+ id: {
385
+ toString: () => 'test-id-alarm',
386
+ name: 'alarm-do',
387
+ equals: () => false,
388
+ },
389
+ storage: {},
390
+ blockConcurrencyWhile: vi.fn(),
391
+ waitUntil: vi.fn(),
392
+ } as unknown as DurableObjectState;
393
+
394
+ const instance = new InstrumentedDO(mockState, {});
395
+ await instance.alarm();
396
+
397
+ expect(mockTracer.startActiveSpan).toHaveBeenCalled();
398
+
399
+ const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
400
+ expect(spanName).toContain('DO');
401
+ expect(spanName).toContain('alarm-do');
402
+ expect(spanName).toContain('alarm');
403
+ });
404
+
405
+ it('should add DO-specific attributes for alarm()', async () => {
406
+ class TestDO {
407
+ constructor(public state: DurableObjectState, public env: any) {}
408
+
409
+ async fetch(request: Request) {
410
+ return new Response('OK');
411
+ }
412
+
413
+ async alarm() {
414
+ // Alarm logic
415
+ }
416
+ }
417
+
418
+ const InstrumentedDO = instrumentDO(TestDO, {
419
+ service: { name: 'test-do' },
420
+ });
421
+
422
+ const mockState = {
423
+ id: {
424
+ toString: () => 'alarm-id-123',
425
+ name: 'cleanup-do',
426
+ equals: () => false,
427
+ },
428
+ storage: {},
429
+ blockConcurrencyWhile: vi.fn(),
430
+ waitUntil: vi.fn(),
431
+ } as unknown as DurableObjectState;
432
+
433
+ const instance = new InstrumentedDO(mockState, {});
434
+ await instance.alarm();
435
+
436
+ const options = mockTracer.startActiveSpan.mock.calls[0][1];
437
+ expect(options.kind).toBe(SpanKind.INTERNAL);
438
+ expect(options.attributes['do.id']).toBe('alarm-id-123');
439
+ expect(options.attributes['do.id.name']).toBe('cleanup-do');
440
+ expect(options.attributes['faas.trigger']).toBe('timer');
441
+ });
442
+
443
+ it('should track cold starts for alarm()', async () => {
444
+ class TestDO {
445
+ constructor(public state: DurableObjectState, public env: any) {}
446
+
447
+ async fetch(request: Request) {
448
+ return new Response('OK');
449
+ }
450
+
451
+ async alarm() {
452
+ // Alarm logic
453
+ }
454
+ }
455
+
456
+ const InstrumentedDO = instrumentDO(TestDO, {
457
+ service: { name: 'test-do' },
458
+ });
459
+
460
+ const mockState = {
461
+ id: {
462
+ toString: () => 'test-id',
463
+ name: 'test',
464
+ equals: () => false,
465
+ },
466
+ storage: {},
467
+ blockConcurrencyWhile: vi.fn(),
468
+ waitUntil: vi.fn(),
469
+ } as unknown as DurableObjectState;
470
+
471
+ const instance = new InstrumentedDO(mockState, {});
472
+
473
+ // First alarm call - should be cold start
474
+ await instance.alarm();
475
+ const firstCallOptions = mockTracer.startActiveSpan.mock.calls[0][1];
476
+ expect(firstCallOptions.attributes['faas.coldstart']).toBe(true);
477
+
478
+ // Second alarm call - should NOT be cold start
479
+ await instance.alarm();
480
+ const secondCallOptions = mockTracer.startActiveSpan.mock.calls[1][1];
481
+ expect(secondCallOptions.attributes['faas.coldstart']).toBe(false);
482
+ });
483
+
484
+ it('should handle alarm() errors', async () => {
485
+ class TestDO {
486
+ constructor(public state: DurableObjectState, public env: any) {}
487
+
488
+ async fetch(request: Request) {
489
+ return new Response('OK');
490
+ }
491
+
492
+ async alarm() {
493
+ throw new Error('Alarm failed');
494
+ }
495
+ }
496
+
497
+ const InstrumentedDO = instrumentDO(TestDO, {
498
+ service: { name: 'test-do' },
499
+ });
500
+
501
+ const mockState = {
502
+ id: {
503
+ toString: () => 'test-id',
504
+ name: 'test',
505
+ equals: () => false,
506
+ },
507
+ storage: {},
508
+ blockConcurrencyWhile: vi.fn(),
509
+ waitUntil: vi.fn(),
510
+ } as unknown as DurableObjectState;
511
+
512
+ const instance = new InstrumentedDO(mockState, {});
513
+
514
+ await expect(instance.alarm()).rejects.toThrow('Alarm failed');
515
+
516
+ expect(mockSpan.recordException).toHaveBeenCalled();
517
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
518
+ code: SpanStatusCode.ERROR,
519
+ message: 'Alarm failed',
520
+ });
521
+ expect(mockSpan.end).toHaveBeenCalled();
522
+ });
523
+ });
524
+ });