autotel-cloudflare 2.15.0 → 2.17.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 (37) hide show
  1. package/README.md +3 -3
  2. package/dist/bindings.d.ts +0 -19
  3. package/dist/bindings.js +1 -1
  4. package/dist/{chunk-4UG2QCPQ.js → chunk-NUNTBJWB.js} +80 -58
  5. package/dist/chunk-NUNTBJWB.js.map +1 -0
  6. package/dist/index.js +15 -5
  7. package/dist/index.js.map +1 -1
  8. package/dist/parse-error.d.ts +1 -0
  9. package/dist/parse-error.js +3 -0
  10. package/dist/parse-error.js.map +1 -0
  11. package/dist/sampling.d.ts +1 -0
  12. package/package.json +6 -2
  13. package/src/bindings/ai.test.ts +14 -0
  14. package/src/bindings/ai.ts +2 -2
  15. package/src/bindings/analytics-engine.test.ts +15 -0
  16. package/src/bindings/analytics-engine.ts +2 -2
  17. package/src/bindings/bindings-cache.test.ts +80 -0
  18. package/src/bindings/bindings-this-binding.test.ts +258 -0
  19. package/src/bindings/bindings.ts +80 -48
  20. package/src/bindings/browser-rendering.test.ts +16 -0
  21. package/src/bindings/browser-rendering.ts +2 -2
  22. package/src/bindings/hyperdrive.test.ts +22 -0
  23. package/src/bindings/hyperdrive.ts +2 -2
  24. package/src/bindings/images.test.ts +33 -0
  25. package/src/bindings/images.ts +4 -4
  26. package/src/bindings/queue-producer.test.ts +32 -0
  27. package/src/bindings/queue-producer.ts +4 -4
  28. package/src/bindings/rate-limiter.test.ts +16 -0
  29. package/src/bindings/rate-limiter.ts +2 -2
  30. package/src/bindings/vectorize.test.ts +22 -0
  31. package/src/bindings/vectorize.ts +2 -2
  32. package/src/global/fetch.test.ts +55 -0
  33. package/src/global/fetch.ts +4 -2
  34. package/src/parse-error.ts +1 -0
  35. package/src/wrappers/instrument.integration.test.ts +103 -0
  36. package/src/wrappers/instrument.ts +16 -3
  37. package/dist/chunk-4UG2QCPQ.js.map +0 -1
@@ -22,7 +22,7 @@ export function instrumentAI<T extends Ai>(ai: T, bindingName?: string): T {
22
22
 
23
23
  if (prop === 'run' && typeof value === 'function') {
24
24
  return new Proxy(value, {
25
- apply: (fnTarget, thisArg, args) => {
25
+ apply: (fnTarget, _thisArg, args) => {
26
26
  const [model] = args as [string, unknown, unknown];
27
27
  const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
28
28
 
@@ -38,7 +38,7 @@ export function instrumentAI<T extends Ai>(ai: T, bindingName?: string): T {
38
38
  },
39
39
  async (span) => {
40
40
  try {
41
- const result = await Reflect.apply(fnTarget, thisArg, args);
41
+ const result = await Reflect.apply(fnTarget, target, args);
42
42
  if (result?.usage?.prompt_tokens !== undefined) {
43
43
  setAttr(span, 'gen_ai.usage.input_tokens', Number(result.usage.prompt_tokens));
44
44
  }
@@ -145,6 +145,21 @@ describe('Analytics Engine Instrumentation', () => {
145
145
  });
146
146
  });
147
147
 
148
+ describe('this-binding', () => {
149
+ it('should invoke writeDataPoint() with original object as this, not the proxy', () => {
150
+ let receivedThis: any;
151
+ const mockAEObj = {
152
+ writeDataPoint: vi.fn(function(this: any) {
153
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
154
+ receivedThis = this;
155
+ }),
156
+ };
157
+ const instrumented = instrumentAnalyticsEngine(mockAEObj as any, 'test');
158
+ instrumented.writeDataPoint({ indexes: ['idx1'] });
159
+ expect(receivedThis).toBe(mockAEObj);
160
+ });
161
+ });
162
+
148
163
  describe('Non-instrumented methods', () => {
149
164
  it('should pass through non-instrumented methods unchanged', () => {
150
165
  const instrumented = instrumentAnalyticsEngine(mockAE, 'my-dataset');
@@ -22,7 +22,7 @@ export function instrumentAnalyticsEngine<T extends AnalyticsEngineDataset>(ae:
22
22
 
23
23
  if (prop === 'writeDataPoint' && typeof value === 'function') {
24
24
  return new Proxy(value, {
25
- apply: (fnTarget, thisArg, args) => {
25
+ apply: (fnTarget, _thisArg, args) => {
26
26
  const [dataPoint] = args as [AnalyticsEngineDataPoint | undefined];
27
27
  const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
28
28
 
@@ -52,7 +52,7 @@ export function instrumentAnalyticsEngine<T extends AnalyticsEngineDataset>(ae:
52
52
  (span) => {
53
53
  try {
54
54
  // writeDataPoint is synchronous/void
55
- Reflect.apply(fnTarget, thisArg, args);
55
+ Reflect.apply(fnTarget, target, args);
56
56
  span.setStatus({ code: SpanStatusCode.OK });
57
57
  } catch (error) {
58
58
  span.recordException(error as Error);
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { trace } from '@opentelemetry/api';
3
+ import { instrumentBindings } from './bindings';
4
+ import { isWrapped } from './common';
5
+
6
+ describe('instrumentBindings() caching', () => {
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, fn) => {
30
+ return fn(mockSpan);
31
+ }),
32
+ };
33
+
34
+ getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
35
+ });
36
+
37
+ afterEach(() => {
38
+ getTracerSpy.mockRestore();
39
+ });
40
+
41
+ it('should return the same instrumented object for the same env reference', () => {
42
+ const env = {
43
+ MY_KV: { get: vi.fn(), put: vi.fn(), delete: vi.fn(), list: vi.fn() },
44
+ };
45
+
46
+ const first = instrumentBindings(env);
47
+ const second = instrumentBindings(env);
48
+
49
+ expect(first).toBe(second);
50
+ });
51
+
52
+ it('should return different instrumented objects for different env references', () => {
53
+ const env1 = {
54
+ MY_KV: { get: vi.fn(), put: vi.fn(), delete: vi.fn(), list: vi.fn() },
55
+ };
56
+ const env2 = {
57
+ MY_KV: { get: vi.fn(), put: vi.fn(), delete: vi.fn(), list: vi.fn() },
58
+ };
59
+
60
+ const first = instrumentBindings(env1);
61
+ const second = instrumentBindings(env2);
62
+
63
+ expect(first).not.toBe(second);
64
+ });
65
+
66
+ it('should correctly instrument bindings even when returning from cache', () => {
67
+ const env = {
68
+ MY_KV: { get: vi.fn(), put: vi.fn(), delete: vi.fn(), list: vi.fn() },
69
+ API_KEY: 'secret',
70
+ };
71
+
72
+ const first = instrumentBindings(env);
73
+ const second = instrumentBindings(env);
74
+
75
+ // Cached result should have instrumented bindings
76
+ expect(isWrapped(second.MY_KV)).toBe(true);
77
+ // Non-object values pass through
78
+ expect(second.API_KEY).toBe('secret');
79
+ });
80
+ });
@@ -0,0 +1,258 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
3
+ import { instrumentKV, instrumentR2, instrumentD1, instrumentServiceBinding } from './bindings';
4
+
5
+ describe('Bindings this-binding tests', () => {
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
+ describe('KV this-binding', () => {
41
+ it('should invoke get() with original object as this, not the proxy', async () => {
42
+ let receivedThis: any;
43
+ const mockKV = {
44
+ get: vi.fn(async function(this: any) {
45
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
46
+ receivedThis = this;
47
+ return 'value';
48
+ }),
49
+ put: vi.fn(async () => undefined),
50
+ delete: vi.fn(async () => undefined),
51
+ list: vi.fn(async () => ({ keys: [], list_complete: true, cacheStatus: null })),
52
+ } as unknown as KVNamespace;
53
+ const instrumented = instrumentKV(mockKV, 'test');
54
+ await instrumented.get('key');
55
+ expect(receivedThis).toBe(mockKV);
56
+ });
57
+
58
+ it('should invoke put() with original object as this, not the proxy', async () => {
59
+ let receivedThis: any;
60
+ const mockKV = {
61
+ get: vi.fn(async () => null),
62
+ put: vi.fn(async function(this: any) {
63
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
64
+ receivedThis = this;
65
+ }),
66
+ delete: vi.fn(async () => undefined),
67
+ list: vi.fn(async () => ({ keys: [], list_complete: true, cacheStatus: null })),
68
+ } as unknown as KVNamespace;
69
+ const instrumented = instrumentKV(mockKV, 'test');
70
+ await instrumented.put('key', 'value');
71
+ expect(receivedThis).toBe(mockKV);
72
+ });
73
+
74
+ it('should invoke delete() with original object as this, not the proxy', async () => {
75
+ let receivedThis: any;
76
+ const mockKV = {
77
+ get: vi.fn(async () => null),
78
+ put: vi.fn(async () => undefined),
79
+ delete: vi.fn(async function(this: any) {
80
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
81
+ receivedThis = this;
82
+ }),
83
+ list: vi.fn(async () => ({ keys: [], list_complete: true, cacheStatus: null })),
84
+ } as unknown as KVNamespace;
85
+ const instrumented = instrumentKV(mockKV, 'test');
86
+ await instrumented.delete('key');
87
+ expect(receivedThis).toBe(mockKV);
88
+ });
89
+
90
+ it('should invoke list() with original object as this, not the proxy', async () => {
91
+ let receivedThis: any;
92
+ const mockKV = {
93
+ get: vi.fn(async () => null),
94
+ put: vi.fn(async () => undefined),
95
+ delete: vi.fn(async () => undefined),
96
+ list: vi.fn(async function(this: any) {
97
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
98
+ receivedThis = this;
99
+ return { keys: [], list_complete: true, cacheStatus: null };
100
+ }),
101
+ } as unknown as KVNamespace;
102
+ const instrumented = instrumentKV(mockKV, 'test');
103
+ await instrumented.list();
104
+ expect(receivedThis).toBe(mockKV);
105
+ });
106
+ });
107
+
108
+ describe('R2 this-binding', () => {
109
+ it('should invoke get() with original object as this, not the proxy', async () => {
110
+ let receivedThis: any;
111
+ const mockR2 = {
112
+ get: vi.fn(async function(this: any) {
113
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
114
+ receivedThis = this;
115
+ return { size: 100, etag: 'abc', httpMetadata: {} };
116
+ }),
117
+ put: vi.fn(async () => ({ etag: 'abc', uploaded: new Date() })),
118
+ delete: vi.fn(async () => undefined),
119
+ list: vi.fn(async () => ({ objects: [], truncated: false })),
120
+ head: vi.fn(async () => null),
121
+ } as unknown as R2Bucket;
122
+ const instrumented = instrumentR2(mockR2, 'test');
123
+ await instrumented.get('key');
124
+ expect(receivedThis).toBe(mockR2);
125
+ });
126
+
127
+ it('should invoke put() with original object as this, not the proxy', async () => {
128
+ let receivedThis: any;
129
+ const mockR2 = {
130
+ get: vi.fn(async () => null),
131
+ put: vi.fn(async function(this: any) {
132
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
133
+ receivedThis = this;
134
+ return { etag: 'abc', uploaded: new Date() };
135
+ }),
136
+ delete: vi.fn(async () => undefined),
137
+ list: vi.fn(async () => ({ objects: [], truncated: false })),
138
+ head: vi.fn(async () => null),
139
+ } as unknown as R2Bucket;
140
+ const instrumented = instrumentR2(mockR2, 'test');
141
+ await instrumented.put('key', 'value');
142
+ expect(receivedThis).toBe(mockR2);
143
+ });
144
+
145
+ it('should invoke delete() with original object as this, not the proxy', async () => {
146
+ let receivedThis: any;
147
+ const mockR2 = {
148
+ get: vi.fn(async () => null),
149
+ put: vi.fn(async () => ({ etag: 'abc', uploaded: new Date() })),
150
+ delete: vi.fn(async function(this: any) {
151
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
152
+ receivedThis = this;
153
+ }),
154
+ list: vi.fn(async () => ({ objects: [], truncated: false })),
155
+ head: vi.fn(async () => null),
156
+ } as unknown as R2Bucket;
157
+ const instrumented = instrumentR2(mockR2, 'test');
158
+ await instrumented.delete('key');
159
+ expect(receivedThis).toBe(mockR2);
160
+ });
161
+
162
+ it('should invoke list() with original object as this, not the proxy', async () => {
163
+ let receivedThis: any;
164
+ const mockR2 = {
165
+ get: vi.fn(async () => null),
166
+ put: vi.fn(async () => ({ etag: 'abc', uploaded: new Date() })),
167
+ delete: vi.fn(async () => undefined),
168
+ list: vi.fn(async function(this: any) {
169
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
170
+ receivedThis = this;
171
+ return { objects: [], truncated: false };
172
+ }),
173
+ head: vi.fn(async () => null),
174
+ } as unknown as R2Bucket;
175
+ const instrumented = instrumentR2(mockR2, 'test');
176
+ await instrumented.list();
177
+ expect(receivedThis).toBe(mockR2);
178
+ });
179
+ });
180
+
181
+ describe('D1 this-binding', () => {
182
+ it('should invoke prepare() with original object as this, not the proxy', () => {
183
+ let receivedThis: any;
184
+ const mockPrepared = {
185
+ first: vi.fn(async () => null),
186
+ run: vi.fn(async () => ({})),
187
+ all: vi.fn(async () => []),
188
+ raw: vi.fn(async () => []),
189
+ bind: vi.fn(function() { return mockPrepared; }),
190
+ };
191
+ const mockD1 = {
192
+ prepare: vi.fn(function(this: any) {
193
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
194
+ receivedThis = this;
195
+ return mockPrepared;
196
+ }),
197
+ exec: vi.fn(async () => ({ count: 0 })),
198
+ } as unknown as D1Database;
199
+ const instrumented = instrumentD1(mockD1, 'test');
200
+ instrumented.prepare('SELECT 1');
201
+ expect(receivedThis).toBe(mockD1);
202
+ });
203
+
204
+ it('should invoke prepared statement methods with original prepared object as this, not the proxy', async () => {
205
+ let receivedThis: any;
206
+ const mockPrepared = {
207
+ first: vi.fn(async function(this: any) {
208
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
209
+ receivedThis = this;
210
+ return { id: 1 };
211
+ }),
212
+ run: vi.fn(async () => ({})),
213
+ all: vi.fn(async () => []),
214
+ raw: vi.fn(async () => []),
215
+ bind: vi.fn(function() { return mockPrepared; }),
216
+ };
217
+ const mockD1 = {
218
+ prepare: vi.fn(() => mockPrepared),
219
+ exec: vi.fn(async () => ({ count: 0 })),
220
+ } as unknown as D1Database;
221
+ const instrumented = instrumentD1(mockD1, 'test');
222
+ const stmt = instrumented.prepare('SELECT * FROM users WHERE id = ?');
223
+ await stmt.first();
224
+ expect(receivedThis).toBe(mockPrepared);
225
+ });
226
+
227
+ it('should invoke exec() with original object as this, not the proxy', async () => {
228
+ let receivedThis: any;
229
+ const mockD1 = {
230
+ prepare: vi.fn(() => ({ first: vi.fn(), run: vi.fn(), all: vi.fn(), raw: vi.fn() })),
231
+ exec: vi.fn(async function(this: any) {
232
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
233
+ receivedThis = this;
234
+ return { count: 1 };
235
+ }),
236
+ } as unknown as D1Database;
237
+ const instrumented = instrumentD1(mockD1, 'test');
238
+ await instrumented.exec('CREATE TABLE test (id INTEGER PRIMARY KEY)');
239
+ expect(receivedThis).toBe(mockD1);
240
+ });
241
+ });
242
+
243
+ describe('Service Binding this-binding', () => {
244
+ it('should invoke fetch() with original object as this, not the proxy', async () => {
245
+ let receivedThis: any;
246
+ const mockFetcher = {
247
+ fetch: vi.fn(async function(this: any) {
248
+ // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
249
+ receivedThis = this;
250
+ return new Response('ok', { status: 200 });
251
+ }),
252
+ } as unknown as Fetcher;
253
+ const instrumented = instrumentServiceBinding(mockFetcher, 'test');
254
+ await instrumented.fetch('https://example.com');
255
+ expect(receivedThis).toBe(mockFetcher);
256
+ });
257
+ });
258
+ });