autotel-cloudflare 2.12.0 → 2.14.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 (44) hide show
  1. package/dist/actors.js +1 -1
  2. package/dist/bindings.d.ts +113 -1
  3. package/dist/bindings.js +2 -2
  4. package/dist/chunk-4UG2QCPQ.js +1060 -0
  5. package/dist/chunk-4UG2QCPQ.js.map +1 -0
  6. package/dist/{chunk-5NL62W4L.js → chunk-O4IYKWPJ.js} +8 -3
  7. package/dist/chunk-O4IYKWPJ.js.map +1 -0
  8. package/dist/{chunk-ADWSZ5GY.js → chunk-WDNZVVRW.js} +8 -9
  9. package/dist/chunk-WDNZVVRW.js.map +1 -0
  10. package/dist/handlers.d.ts +5 -14
  11. package/dist/handlers.js +2 -2
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +34 -6
  14. package/dist/index.js.map +1 -1
  15. package/package.json +2 -2
  16. package/src/bindings/ai.test.ts +156 -0
  17. package/src/bindings/ai.ts +71 -0
  18. package/src/bindings/analytics-engine.test.ts +160 -0
  19. package/src/bindings/analytics-engine.ts +78 -0
  20. package/src/bindings/bindings-detection.test.ts +235 -0
  21. package/src/bindings/bindings.ts +98 -47
  22. package/src/bindings/browser-rendering.test.ts +144 -0
  23. package/src/bindings/browser-rendering.ts +70 -0
  24. package/src/bindings/common.ts +9 -0
  25. package/src/bindings/hyperdrive.test.ts +154 -0
  26. package/src/bindings/hyperdrive.ts +74 -0
  27. package/src/bindings/images.test.ts +229 -0
  28. package/src/bindings/images.ts +182 -0
  29. package/src/bindings/index.ts +8 -0
  30. package/src/bindings/queue-producer.test.ts +192 -0
  31. package/src/bindings/queue-producer.ts +105 -0
  32. package/src/bindings/rate-limiter.test.ts +124 -0
  33. package/src/bindings/rate-limiter.ts +69 -0
  34. package/src/bindings/vectorize.test.ts +340 -0
  35. package/src/bindings/vectorize.ts +86 -0
  36. package/src/handlers/workflows.test.ts +325 -0
  37. package/src/handlers/workflows.ts +51 -41
  38. package/src/index.ts +8 -0
  39. package/src/wrappers/cf-attributes.test.ts +275 -0
  40. package/src/wrappers/instrument.ts +38 -0
  41. package/dist/chunk-5NL62W4L.js.map +0 -1
  42. package/dist/chunk-ADWSZ5GY.js.map +0 -1
  43. package/dist/chunk-UPQE3J4I.js +0 -520
  44. package/dist/chunk-UPQE3J4I.js.map +0 -1
@@ -0,0 +1,340 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
3
+ import { instrumentVectorize } from './vectorize';
4
+
5
+ describe('Vectorize Binding Instrumentation', () => {
6
+ let mockTracer: any;
7
+ let mockSpan: any;
8
+ let getTracerSpy: any;
9
+ let mockVectorize: 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
+ mockVectorize = {
37
+ query: vi.fn(async () => ({
38
+ matches: [
39
+ { id: 'vec-1', score: 0.95 },
40
+ { id: 'vec-2', score: 0.87 },
41
+ { id: 'vec-3', score: 0.72 },
42
+ ],
43
+ count: 3,
44
+ })),
45
+ insert: vi.fn(async () => ({ mutationId: 'mut-1', count: 2 })),
46
+ upsert: vi.fn(async () => ({ mutationId: 'mut-2', count: 3 })),
47
+ deleteByIds: vi.fn(async () => ({ mutationId: 'mut-3', count: 1 })),
48
+ getByIds: vi.fn(async () => [{ id: 'vec-1', values: [0.1, 0.2] }]),
49
+ describe: vi.fn(async () => ({
50
+ dimensions: 128,
51
+ vectorCount: 1000,
52
+ processedUpTo: 'ts-123',
53
+ })),
54
+ // Non-instrumented method
55
+ toString: vi.fn(() => 'VectorizeIndex'),
56
+ } as unknown as VectorizeIndex;
57
+ });
58
+
59
+ describe('query()', () => {
60
+ it('should create span with correct attributes', async () => {
61
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
62
+
63
+ // The source reads topK from args[0], so we pass a query object as the first argument
64
+ await instrumented.query({ topK: 5 } as any, {} as any);
65
+
66
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
67
+ 'Vectorize my-index: query',
68
+ expect.objectContaining({
69
+ kind: SpanKind.CLIENT,
70
+ attributes: expect.objectContaining({
71
+ 'db.system': 'cloudflare-vectorize',
72
+ 'db.operation': 'query',
73
+ 'db.collection.name': 'my-index',
74
+ 'db.vectorize.top_k': 5,
75
+ }),
76
+ }),
77
+ expect.any(Function),
78
+ );
79
+ });
80
+
81
+ it('should record matches_count from result', async () => {
82
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
83
+
84
+ await instrumented.query([0.1, 0.2, 0.3] as any, {} as any);
85
+
86
+ expect(mockSpan.setAttribute).toHaveBeenCalledWith('db.vectorize.matches_count', 3);
87
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
88
+ expect(mockSpan.end).toHaveBeenCalled();
89
+ });
90
+
91
+ it('should handle query without topK in first argument', async () => {
92
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
93
+
94
+ await instrumented.query([0.1, 0.2, 0.3] as any, {} as any);
95
+
96
+ const attributes = mockTracer.startActiveSpan.mock.calls[0][1].attributes;
97
+ expect(attributes['db.vectorize.top_k']).toBeUndefined();
98
+ });
99
+ });
100
+
101
+ describe('insert()', () => {
102
+ it('should set vectors_count from input array length', async () => {
103
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
104
+
105
+ const vectors = [
106
+ { id: 'vec-1', values: [0.1, 0.2] },
107
+ { id: 'vec-2', values: [0.3, 0.4] },
108
+ ];
109
+ await instrumented.insert(vectors as any);
110
+
111
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
112
+ 'Vectorize my-index: insert',
113
+ expect.objectContaining({
114
+ kind: SpanKind.CLIENT,
115
+ attributes: expect.objectContaining({
116
+ 'db.system': 'cloudflare-vectorize',
117
+ 'db.operation': 'insert',
118
+ 'db.collection.name': 'my-index',
119
+ 'db.vectorize.vectors_count': 2,
120
+ }),
121
+ }),
122
+ expect.any(Function),
123
+ );
124
+
125
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
126
+ expect(mockSpan.end).toHaveBeenCalled();
127
+ });
128
+ });
129
+
130
+ describe('upsert()', () => {
131
+ it('should set vectors_count from input array length', async () => {
132
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
133
+
134
+ const vectors = [
135
+ { id: 'vec-1', values: [0.1, 0.2] },
136
+ { id: 'vec-2', values: [0.3, 0.4] },
137
+ { id: 'vec-3', values: [0.5, 0.6] },
138
+ ];
139
+ await instrumented.upsert(vectors as any);
140
+
141
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
142
+ 'Vectorize my-index: upsert',
143
+ expect.objectContaining({
144
+ kind: SpanKind.CLIENT,
145
+ attributes: expect.objectContaining({
146
+ 'db.system': 'cloudflare-vectorize',
147
+ 'db.operation': 'upsert',
148
+ 'db.collection.name': 'my-index',
149
+ 'db.vectorize.vectors_count': 3,
150
+ }),
151
+ }),
152
+ expect.any(Function),
153
+ );
154
+
155
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
156
+ expect(mockSpan.end).toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ describe('deleteByIds()', () => {
161
+ it('should create correct span', async () => {
162
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
163
+
164
+ await instrumented.deleteByIds(['vec-1', 'vec-2']);
165
+
166
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
167
+ 'Vectorize my-index: deleteByIds',
168
+ expect.objectContaining({
169
+ kind: SpanKind.CLIENT,
170
+ attributes: expect.objectContaining({
171
+ 'db.system': 'cloudflare-vectorize',
172
+ 'db.operation': 'deleteByIds',
173
+ 'db.collection.name': 'my-index',
174
+ }),
175
+ }),
176
+ expect.any(Function),
177
+ );
178
+
179
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
180
+ expect(mockSpan.end).toHaveBeenCalled();
181
+ });
182
+ });
183
+
184
+ describe('describe()', () => {
185
+ it('should create correct span', async () => {
186
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
187
+
188
+ await instrumented.describe();
189
+
190
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
191
+ 'Vectorize my-index: describe',
192
+ expect.objectContaining({
193
+ kind: SpanKind.CLIENT,
194
+ attributes: expect.objectContaining({
195
+ 'db.system': 'cloudflare-vectorize',
196
+ 'db.operation': 'describe',
197
+ 'db.collection.name': 'my-index',
198
+ }),
199
+ }),
200
+ expect.any(Function),
201
+ );
202
+
203
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
204
+ expect(mockSpan.end).toHaveBeenCalled();
205
+ });
206
+ });
207
+
208
+ describe('Error handling', () => {
209
+ it('should record exception and set error status on query() failure', async () => {
210
+ mockVectorize.query = vi.fn(async () => {
211
+ throw new Error('Vectorize query failed');
212
+ });
213
+
214
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
215
+
216
+ await expect(instrumented.query([0.1, 0.2] as any, {} as any)).rejects.toThrow('Vectorize query failed');
217
+
218
+ expect(mockSpan.recordException).toHaveBeenCalled();
219
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
220
+ code: SpanStatusCode.ERROR,
221
+ message: 'Vectorize query failed',
222
+ });
223
+ expect(mockSpan.end).toHaveBeenCalled();
224
+ });
225
+
226
+ it('should record exception and set error status on insert() failure', async () => {
227
+ mockVectorize.insert = vi.fn(async () => {
228
+ throw new Error('Insert failed');
229
+ });
230
+
231
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
232
+
233
+ await expect(instrumented.insert([{ id: 'v1', values: [0.1] }] as any)).rejects.toThrow('Insert failed');
234
+
235
+ expect(mockSpan.recordException).toHaveBeenCalled();
236
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
237
+ code: SpanStatusCode.ERROR,
238
+ message: 'Insert failed',
239
+ });
240
+ expect(mockSpan.end).toHaveBeenCalled();
241
+ });
242
+
243
+ it('should record exception and set error status on upsert() failure', async () => {
244
+ mockVectorize.upsert = vi.fn(async () => {
245
+ throw new Error('Upsert failed');
246
+ });
247
+
248
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
249
+
250
+ await expect(instrumented.upsert([{ id: 'v1', values: [0.1] }] as any)).rejects.toThrow('Upsert failed');
251
+
252
+ expect(mockSpan.recordException).toHaveBeenCalled();
253
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
254
+ code: SpanStatusCode.ERROR,
255
+ message: 'Upsert failed',
256
+ });
257
+ expect(mockSpan.end).toHaveBeenCalled();
258
+ });
259
+
260
+ it('should record exception and set error status on deleteByIds() failure', async () => {
261
+ mockVectorize.deleteByIds = vi.fn(async () => {
262
+ throw new Error('Delete failed');
263
+ });
264
+
265
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
266
+
267
+ await expect(instrumented.deleteByIds(['vec-1'])).rejects.toThrow('Delete failed');
268
+
269
+ expect(mockSpan.recordException).toHaveBeenCalled();
270
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
271
+ code: SpanStatusCode.ERROR,
272
+ message: 'Delete failed',
273
+ });
274
+ expect(mockSpan.end).toHaveBeenCalled();
275
+ });
276
+
277
+ it('should record exception and set error status on describe() failure', async () => {
278
+ mockVectorize.describe = vi.fn(async () => {
279
+ throw new Error('Describe failed');
280
+ });
281
+
282
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
283
+
284
+ await expect(instrumented.describe()).rejects.toThrow('Describe failed');
285
+
286
+ expect(mockSpan.recordException).toHaveBeenCalled();
287
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
288
+ code: SpanStatusCode.ERROR,
289
+ message: 'Describe failed',
290
+ });
291
+ expect(mockSpan.end).toHaveBeenCalled();
292
+ });
293
+
294
+ it('should handle non-Error exceptions', async () => {
295
+ mockVectorize.query = vi.fn(async () => {
296
+ throw 'string error';
297
+ });
298
+
299
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
300
+
301
+ await expect(instrumented.query([0.1] as any, {} as any)).rejects.toThrow('string error');
302
+
303
+ expect(mockSpan.recordException).toHaveBeenCalled();
304
+ expect(mockSpan.setStatus).toHaveBeenCalledWith({
305
+ code: SpanStatusCode.ERROR,
306
+ message: 'string error',
307
+ });
308
+ expect(mockSpan.end).toHaveBeenCalled();
309
+ });
310
+ });
311
+
312
+ describe('Non-instrumented methods', () => {
313
+ it('should pass through non-traced methods without creating spans', () => {
314
+ const instrumented = instrumentVectorize(mockVectorize, 'my-index');
315
+
316
+ const result = instrumented.toString();
317
+
318
+ expect(result).toBe('VectorizeIndex');
319
+ expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
320
+ });
321
+ });
322
+
323
+ describe('Default index name', () => {
324
+ it('should use "vectorize" as default index name when none provided', async () => {
325
+ const instrumented = instrumentVectorize(mockVectorize);
326
+
327
+ await instrumented.query([0.1, 0.2] as any, {} as any);
328
+
329
+ expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
330
+ 'Vectorize vectorize: query',
331
+ expect.objectContaining({
332
+ attributes: expect.objectContaining({
333
+ 'db.collection.name': 'vectorize',
334
+ }),
335
+ }),
336
+ expect.any(Function),
337
+ );
338
+ });
339
+ });
340
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Vectorize binding instrumentation
3
+ */
4
+
5
+ import {
6
+ trace,
7
+ SpanKind,
8
+ SpanStatusCode,
9
+ } from '@opentelemetry/api';
10
+ import type { WorkerTracer } from 'autotel-edge';
11
+ import { wrap, setAttr } from './common';
12
+
13
+ const TRACED_METHODS = ['query', 'insert', 'upsert', 'deleteByIds', 'getByIds', 'describe'] as const;
14
+
15
+ /**
16
+ * Instrument Vectorize index binding
17
+ */
18
+ export function instrumentVectorize<T extends VectorizeIndex>(vectorize: T, indexName?: string): T {
19
+ const name = indexName || 'vectorize';
20
+
21
+ const handler: ProxyHandler<T> = {
22
+ get(target, prop) {
23
+ const value = Reflect.get(target, prop);
24
+
25
+ if (typeof prop === 'string' && TRACED_METHODS.includes(prop as any) && typeof value === 'function') {
26
+ return new Proxy(value, {
27
+ apply: (fnTarget, thisArg, args) => {
28
+ const operation = prop as string;
29
+ const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
30
+
31
+ const attributes: Record<string, string | number> = {
32
+ 'db.system': 'cloudflare-vectorize',
33
+ 'db.operation': operation,
34
+ 'db.collection.name': name,
35
+ };
36
+
37
+ // Per-operation attributes
38
+ if (operation === 'query') {
39
+ const queryInput = args[0] as { topK?: number } | undefined;
40
+ if (queryInput?.topK !== undefined) {
41
+ attributes['db.vectorize.top_k'] = queryInput.topK;
42
+ }
43
+ }
44
+
45
+ if ((operation === 'insert' || operation === 'upsert') && Array.isArray(args[0])) {
46
+ attributes['db.vectorize.vectors_count'] = args[0].length;
47
+ }
48
+
49
+ return tracer.startActiveSpan(
50
+ `Vectorize ${name}: ${operation}`,
51
+ {
52
+ kind: SpanKind.CLIENT,
53
+ attributes,
54
+ },
55
+ async (span) => {
56
+ try {
57
+ const result = await Reflect.apply(fnTarget, thisArg, args);
58
+
59
+ if (operation === 'query' && result?.matches) {
60
+ setAttr(span, 'db.vectorize.matches_count', result.matches.length);
61
+ }
62
+
63
+ span.setStatus({ code: SpanStatusCode.OK });
64
+ return result;
65
+ } catch (error) {
66
+ span.recordException(error as Error);
67
+ span.setStatus({
68
+ code: SpanStatusCode.ERROR,
69
+ message: error instanceof Error ? error.message : String(error),
70
+ });
71
+ throw error;
72
+ } finally {
73
+ span.end();
74
+ }
75
+ },
76
+ );
77
+ },
78
+ });
79
+ }
80
+
81
+ return value;
82
+ },
83
+ };
84
+
85
+ return wrap(vectorize, handler);
86
+ }