Skip to content
File

Blob: src/cloudflare/internal/test/vectorize/vectorize-mock.js

javascript206 lines
1// Copyright (c) 2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5/** @type {Array<VectorizeMatch>} */
6const exampleVectorMatches = [
7 {
8 id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',
9 values: [0.2331, 1.0125, 0.6131, 0.9421, 0.9661, 0.8121],
10 metadata: { text: 'She sells seashells by the seashore' },
11 score: 0.71151,
12 },
13 {
14 id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',
15 values: [0.2321, 0.8121, 0.6315, 0.6151, 0.4121, 0.1512],
16 metadata: { text: 'Peter Piper picked a peck of pickled peppers' },
17 score: 0.68913,
18 },
19 {
20 id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',
21 values: [0.0515, 0.7512, 0.8612, 0.2153, 0.15121, 0.6812],
22 metadata: {
23 text: 'You know New York, you need New York, you know you need unique New York',
24 },
25 score: 0.94812,
26 },
27];
28/** @type {Array<VectorizeVector>} */
29const exampleVectors = exampleVectorMatches
30 .filter((m) => typeof m !== 'undefined')
31 .map(({ id, values, metadata }) => ({
32 id,
33 values: values ?? [],
34 metadata: metadata ?? {},
35 }));
36 
37export default {
38 /**
39 * @param {Request} request
40 */
41 async fetch(request) {
42 try {
43 const { pathname } = new URL(request.url);
44 
45 if (request.method === 'POST' && pathname.endsWith('/create')) {
46 /** @type {VectorizeIndexConfig} */
47 const config = await request.json();
48 const name = pathname.split('/')[2];
49 /** @type {VectorizeIndexDetails} */
50 const index = {
51 id: 'ffeb30f5-d349-4ba5-8dde-79da543190fe',
52 name: name || 'my-index',
53 config: config,
54 vectorsCount: 0,
55 };
56 return Response.json(index);
57 } else if (request.method === 'GET' && pathname.endsWith('/list')) {
58 /** @type {Array<VectorizeIndexDetails>} */
59 const index = [
60 {
61 id: '0f48d520-5bf5-4980-acd9-98453fb8a27f',
62 name: 'my-first-index',
63 config: {
64 dimensions: 1536,
65 preset: 'openai/text-embedding-ada-002',
66 metric: 'euclidean',
67 },
68 vectorsCount: 500000,
69 },
70 {
71 id: 'b9fc84af-31f3-449c-bc61-b62abc86d5a1',
72 name: 'my-second-index',
73 config: {
74 dimensions: 1536,
75 metric: 'dot-product',
76 },
77 vectorsCount: 750000,
78 },
79 ];
80 return Response.json(index);
81 } else if (request.method === 'GET' && pathname.split('/').length === 3) {
82 /** @type {VectorizeIndexDetails} */
83 const index = {
84 id: 'ffeb30f5-d349-4ba5-8dde-79da543190fe',
85 name: pathname.split('/')[2] || 'my-index',
86 config: {
87 dimensions: 1536,
88 preset: 'openai/text-embedding-ada-002',
89 metric: 'euclidean',
90 },
91 vectorsCount: 850850,
92 };
93 return Response.json(index);
94 } else if (
95 request.method === 'DELETE' &&
96 pathname.split('/').length === 2
97 ) {
98 return Response.json({});
99 } else if (request.method === 'POST' && pathname.endsWith('/query')) {
100 /** @type {VectorizeQueryOptions & ({vector: number[]} | {vectorId: string})} */
101 const body = await request.json();
102 let returnSet = structuredClone(exampleVectorMatches);
103 if (
104 body?.filter?.['text'] &&
105 typeof body?.filter?.['text'] === 'object'
106 ) {
107 if (body?.filter?.['text']?.['$eq'] !== undefined) {
108 const criteria = body?.filter?.['text']?.['$eq'];
109 returnSet = returnSet.filter(
110 (m) => m.metadata?.['text'] === criteria
111 );
112 }
113 if (body?.filter?.['text']?.['$in'] !== undefined) {
114 const criteria = body?.filter?.['text']?.['$in'];
115 returnSet = returnSet.filter((m) =>
116 criteria.includes(m.metadata?.['text'])
117 );
118 }
119 if (body?.filter?.['text']?.['$gt'] !== undefined) {
120 const criteria = body?.filter?.['text']?.['$gt'];
121 returnSet = returnSet.filter(
122 (m) => m.metadata?.['text'] > criteria
123 );
124 }
125 if (body?.filter?.['text']?.['$lt'] !== undefined) {
126 const criteria = body?.filter?.['text']?.['$lt'];
127 returnSet = returnSet.filter(
128 (m) => m.metadata?.['text'] < criteria
129 );
130 }
131 }
132 
133 if (!body?.returnValues)
134 returnSet.forEach((v) => {
135 delete v.values;
136 });
137 if (!body?.returnMetadata || body?.returnMetadata === 'none')
138 returnSet.forEach((v) => {
139 delete v.metadata;
140 });
141 return Response.json({
142 matches: returnSet,
143 count: returnSet.length,
144 });
145 } else if (request.method === 'POST' && pathname.endsWith('/insert')) {
146 /** @type {{vectors: Array<VectorizeVector>}} */
147 const data = await request.json();
148 if (data.vectors.find((v) => v.id === 'fail-with-test-error')) {
149 return Response.json(
150 {
151 code: 9999,
152 error: 'You asked me for this error',
153 },
154 {
155 status: 400,
156 }
157 );
158 }
159 
160 /** @type {VectorizeAsyncMutation} */
161 const res = {
162 // fudge a bit and set the mutation id to some internals so our asserts can check more
163 mutationId: `total vectors: ${data.vectors.length + exampleVectors.length}`,
164 };
165 return Response.json(res);
166 } else if (request.method === 'POST' && pathname.endsWith('/upsert')) {
167 /** @type {{vectors: Array<VectorizeVector>}} */
168 let data = await request.json();
169 if (data.vectors.length > 1) data.vectors.splice(-1);
170 /** @type {VectorizeAsyncMutation} */
171 const res = {
172 // fudge a bit and set the mutation id to some internals so our asserts can check more
173 mutationId: `total vectors: ${data.vectors.length + exampleVectors.length}`,
174 };
175 return Response.json(res);
176 } else if (
177 request.method === 'POST' &&
178 pathname.endsWith('/deleteByIds')
179 ) {
180 /** @type {{ids: Array<string>}} */
181 const body = await request.json();
182 /** @type {VectorizeAsyncMutation} */
183 const res = {
184 // fudge a bit and set the mutation id to some internals so our asserts can check more
185 mutationId: `deleted vectors: ${body.ids.length}`,
186 };
187 return Response.json(res);
188 } else if (request.method === 'POST' && pathname.endsWith('/getByIds')) {
189 /** @type {{ids: Array<string>}} */
190 const body = await request.json();
191 return Response.json(
192 exampleVectors.filter(({ id }) => body.ids.includes(id))
193 );
194 } else {
195 return Response.json({ error: 'Not found' }, { status: 404 });
196 }
197 } catch (err) {
198 return Response.json(
199 // @ts-expect-error - err is typed as unknown but has message and stack
200 { error: err.message, stack: err.stack },
201 { status: 500 }
202 );
203 }
204 },
205};