@mastra/couchbase 0.0.0-trigger-playground-ui-package-20250506151043 → 0.0.0-vector-query-sources-20250516172905
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.
- package/CHANGELOG.md +129 -2
- package/dist/_tsup-dts-rollup.d.cts +50 -3
- package/dist/_tsup-dts-rollup.d.ts +50 -3
- package/dist/index.cjs +175 -80
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +176 -81
- package/docker-compose.yaml +12 -12
- package/package.json +3 -3
- package/src/vector/index.integration.test.ts +191 -16
- package/src/vector/index.ts +224 -77
- package/src/vector/index.unit.test.ts +54 -54
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MastraVector } from '@mastra/core/vector';
|
|
2
|
-
import { connect, SearchRequest, VectorSearch, VectorQuery } from 'couchbase';
|
|
2
|
+
import { connect, SearchRequest, VectorSearch, VectorQuery, MutateInSpec } from 'couchbase';
|
|
3
3
|
|
|
4
4
|
// src/vector/index.ts
|
|
5
5
|
var DISTANCE_MAPPING = {
|
|
@@ -17,11 +17,32 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
17
17
|
bucket;
|
|
18
18
|
scope;
|
|
19
19
|
vector_dimension;
|
|
20
|
-
constructor(
|
|
20
|
+
constructor(paramsOrConnectionString, username, password, bucketName, scopeName, collectionName) {
|
|
21
|
+
let connectionString_, username_, password_, bucketName_, scopeName_, collectionName_;
|
|
22
|
+
if (typeof paramsOrConnectionString === "object" && paramsOrConnectionString !== null && "connectionString" in paramsOrConnectionString) {
|
|
23
|
+
connectionString_ = paramsOrConnectionString.connectionString;
|
|
24
|
+
username_ = paramsOrConnectionString.username;
|
|
25
|
+
password_ = paramsOrConnectionString.password;
|
|
26
|
+
bucketName_ = paramsOrConnectionString.bucketName;
|
|
27
|
+
scopeName_ = paramsOrConnectionString.scopeName;
|
|
28
|
+
collectionName_ = paramsOrConnectionString.collectionName;
|
|
29
|
+
} else {
|
|
30
|
+
if (arguments.length > 1) {
|
|
31
|
+
console.warn(
|
|
32
|
+
"Deprecation Warning: CouchbaseVector constructor positional arguments are deprecated. Please use a single object parameter instead. This signature will be removed on May 20th, 2025."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
connectionString_ = paramsOrConnectionString;
|
|
36
|
+
username_ = username;
|
|
37
|
+
password_ = password;
|
|
38
|
+
bucketName_ = bucketName;
|
|
39
|
+
scopeName_ = scopeName;
|
|
40
|
+
collectionName_ = collectionName;
|
|
41
|
+
}
|
|
21
42
|
super();
|
|
22
|
-
const baseClusterPromise = connect(
|
|
23
|
-
username,
|
|
24
|
-
password,
|
|
43
|
+
const baseClusterPromise = connect(connectionString_, {
|
|
44
|
+
username: username_,
|
|
45
|
+
password: password_,
|
|
25
46
|
configProfile: "wanDevelopment"
|
|
26
47
|
});
|
|
27
48
|
const telemetry = this.__getTelemetry();
|
|
@@ -32,9 +53,9 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
32
53
|
}
|
|
33
54
|
}) ?? baseClusterPromise;
|
|
34
55
|
this.cluster = null;
|
|
35
|
-
this.bucketName =
|
|
36
|
-
this.collectionName =
|
|
37
|
-
this.scopeName =
|
|
56
|
+
this.bucketName = bucketName_;
|
|
57
|
+
this.collectionName = collectionName_;
|
|
58
|
+
this.scopeName = scopeName_;
|
|
38
59
|
this.collection = null;
|
|
39
60
|
this.bucket = null;
|
|
40
61
|
this.scope = null;
|
|
@@ -57,86 +78,95 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
57
78
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
58
79
|
throw new Error("Dimension must be a positive integer");
|
|
59
80
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
mapping: {
|
|
72
|
-
default_analyzer: "standard",
|
|
73
|
-
default_datetime_parser: "dateTimeOptional",
|
|
74
|
-
default_field: "_all",
|
|
75
|
-
default_mapping: {
|
|
76
|
-
dynamic: true,
|
|
77
|
-
enabled: false
|
|
81
|
+
try {
|
|
82
|
+
await this.scope.searchIndexes().upsertIndex({
|
|
83
|
+
name: indexName,
|
|
84
|
+
sourceName: this.bucketName,
|
|
85
|
+
type: "fulltext-index",
|
|
86
|
+
params: {
|
|
87
|
+
doc_config: {
|
|
88
|
+
docid_prefix_delim: "",
|
|
89
|
+
docid_regexp: "",
|
|
90
|
+
mode: "scope.collection.type_field",
|
|
91
|
+
type_field: "type"
|
|
78
92
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// [Doc](https://docs.couchbase.com/server/current/search/search-index-params.html#params) mentions this attribute is required for vector search to return the indexed field
|
|
85
|
-
type_field: "_type",
|
|
86
|
-
types: {
|
|
87
|
-
[`${this.scopeName}.${this.collectionName}`]: {
|
|
93
|
+
mapping: {
|
|
94
|
+
default_analyzer: "standard",
|
|
95
|
+
default_datetime_parser: "dateTimeOptional",
|
|
96
|
+
default_field: "_all",
|
|
97
|
+
default_mapping: {
|
|
88
98
|
dynamic: true,
|
|
89
|
-
enabled:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
99
|
+
enabled: false
|
|
100
|
+
},
|
|
101
|
+
default_type: "_default",
|
|
102
|
+
docvalues_dynamic: true,
|
|
103
|
+
// [Doc](https://docs.couchbase.com/server/current/search/search-index-params.html#params) mentions this attribute is required for vector search to return the indexed field
|
|
104
|
+
index_dynamic: true,
|
|
105
|
+
store_dynamic: true,
|
|
106
|
+
// [Doc](https://docs.couchbase.com/server/current/search/search-index-params.html#params) mentions this attribute is required for vector search to return the indexed field
|
|
107
|
+
type_field: "_type",
|
|
108
|
+
types: {
|
|
109
|
+
[`${this.scopeName}.${this.collectionName}`]: {
|
|
110
|
+
dynamic: true,
|
|
111
|
+
enabled: true,
|
|
112
|
+
properties: {
|
|
113
|
+
embedding: {
|
|
114
|
+
enabled: true,
|
|
115
|
+
fields: [
|
|
116
|
+
{
|
|
117
|
+
dims: dimension,
|
|
118
|
+
index: true,
|
|
119
|
+
name: "embedding",
|
|
120
|
+
similarity: DISTANCE_MAPPING[metric],
|
|
121
|
+
type: "vector",
|
|
122
|
+
vector_index_optimized_for: "recall",
|
|
123
|
+
store: true,
|
|
124
|
+
// CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
|
|
125
|
+
docvalues: true,
|
|
126
|
+
// CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
|
|
127
|
+
include_term_vectors: true
|
|
128
|
+
// CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
content: {
|
|
133
|
+
enabled: true,
|
|
134
|
+
fields: [
|
|
135
|
+
{
|
|
136
|
+
index: true,
|
|
137
|
+
name: "content",
|
|
138
|
+
store: true,
|
|
139
|
+
type: "text"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
120
143
|
}
|
|
121
144
|
}
|
|
122
145
|
}
|
|
146
|
+
},
|
|
147
|
+
store: {
|
|
148
|
+
indexType: "scorch",
|
|
149
|
+
segmentVersion: 16
|
|
123
150
|
}
|
|
124
151
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
152
|
+
sourceUuid: "",
|
|
153
|
+
sourceParams: {},
|
|
154
|
+
sourceType: "gocbcore",
|
|
155
|
+
planParams: {
|
|
156
|
+
maxPartitionsPerPIndex: 64,
|
|
157
|
+
indexPartitions: 16,
|
|
158
|
+
numReplicas: 0
|
|
128
159
|
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
numReplicas: 0
|
|
160
|
+
});
|
|
161
|
+
this.vector_dimension = dimension;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const message = error?.message || error?.toString();
|
|
164
|
+
if (message && message.toLowerCase().includes("index exists")) {
|
|
165
|
+
await this.validateExistingIndex(indexName, dimension, metric);
|
|
166
|
+
return;
|
|
137
167
|
}
|
|
138
|
-
|
|
139
|
-
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
140
170
|
}
|
|
141
171
|
async upsert(params) {
|
|
142
172
|
const { vectors, metadata, ids } = params;
|
|
@@ -210,7 +240,16 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
210
240
|
const indexes = await this.scope.searchIndexes().getAllIndexes();
|
|
211
241
|
return indexes?.map((index) => index.name) || [];
|
|
212
242
|
}
|
|
213
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Retrieves statistics about a vector index.
|
|
245
|
+
*
|
|
246
|
+
* @param params - The parameters for describing an index
|
|
247
|
+
* @param params.indexName - The name of the index to describe
|
|
248
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
249
|
+
*/
|
|
250
|
+
async describeIndex(...args) {
|
|
251
|
+
const params = this.normalizeArgs("describeIndex", args);
|
|
252
|
+
const { indexName } = params;
|
|
214
253
|
await this.getCollection();
|
|
215
254
|
if (!(await this.listIndexes()).includes(indexName)) {
|
|
216
255
|
throw new Error(`Index ${indexName} does not exist`);
|
|
@@ -227,7 +266,9 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
227
266
|
)
|
|
228
267
|
};
|
|
229
268
|
}
|
|
230
|
-
async deleteIndex(
|
|
269
|
+
async deleteIndex(...args) {
|
|
270
|
+
const params = this.normalizeArgs("deleteIndex", args);
|
|
271
|
+
const { indexName } = params;
|
|
231
272
|
await this.getCollection();
|
|
232
273
|
if (!(await this.listIndexes()).includes(indexName)) {
|
|
233
274
|
throw new Error(`Index ${indexName} does not exist`);
|
|
@@ -235,6 +276,60 @@ var CouchbaseVector = class extends MastraVector {
|
|
|
235
276
|
await this.scope.searchIndexes().dropIndex(indexName);
|
|
236
277
|
this.vector_dimension = null;
|
|
237
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
281
|
+
* @param indexName - The name of the index containing the vector.
|
|
282
|
+
* @param id - The ID of the vector to update.
|
|
283
|
+
* @param update - An object containing the vector and/or metadata to update.
|
|
284
|
+
* @param update.vector - An optional array of numbers representing the new vector.
|
|
285
|
+
* @param update.metadata - An optional record containing the new metadata.
|
|
286
|
+
* @returns A promise that resolves when the update is complete.
|
|
287
|
+
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
288
|
+
*/
|
|
289
|
+
async updateVector(...args) {
|
|
290
|
+
const params = this.normalizeArgs("updateVector", args);
|
|
291
|
+
const { id, update } = params;
|
|
292
|
+
if (!update.vector && !update.metadata) {
|
|
293
|
+
throw new Error("No updates provided");
|
|
294
|
+
}
|
|
295
|
+
if (update.vector && this.vector_dimension && update.vector.length !== this.vector_dimension) {
|
|
296
|
+
throw new Error("Vector dimension mismatch");
|
|
297
|
+
}
|
|
298
|
+
const collection = await this.getCollection();
|
|
299
|
+
try {
|
|
300
|
+
await collection.get(id);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (err.code === 13 || err.message?.includes("document not found")) {
|
|
303
|
+
throw new Error(`Vector with id ${id} does not exist`);
|
|
304
|
+
}
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
const specs = [];
|
|
308
|
+
if (update.vector) specs.push(MutateInSpec.replace("embedding", update.vector));
|
|
309
|
+
if (update.metadata) specs.push(MutateInSpec.replace("metadata", update.metadata));
|
|
310
|
+
await collection.mutateIn(id, specs);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Deletes a vector by its ID.
|
|
314
|
+
* @param indexName - The name of the index containing the vector.
|
|
315
|
+
* @param id - The ID of the vector to delete.
|
|
316
|
+
* @returns A promise that resolves when the deletion is complete.
|
|
317
|
+
* @throws Will throw an error if the deletion operation fails.
|
|
318
|
+
*/
|
|
319
|
+
async deleteVector(...args) {
|
|
320
|
+
const params = this.normalizeArgs("deleteVector", args);
|
|
321
|
+
const { id } = params;
|
|
322
|
+
const collection = await this.getCollection();
|
|
323
|
+
try {
|
|
324
|
+
await collection.get(id);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (err.code === 13 || err.message?.includes("document not found")) {
|
|
327
|
+
throw new Error(`Vector with id ${id} does not exist`);
|
|
328
|
+
}
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
await collection.remove(id);
|
|
332
|
+
}
|
|
238
333
|
};
|
|
239
334
|
|
|
240
335
|
export { CouchbaseVector, DISTANCE_MAPPING };
|
package/docker-compose.yaml
CHANGED
|
@@ -2,20 +2,20 @@ services:
|
|
|
2
2
|
mastra_couchbase_testing:
|
|
3
3
|
image: couchbase:enterprise-7.6.4
|
|
4
4
|
ports:
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
5
|
+
- '8091-8095:8091-8095'
|
|
6
|
+
- '11210:11210'
|
|
7
|
+
- '9102:9102'
|
|
8
8
|
expose:
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- '8091'
|
|
10
|
+
- '8092'
|
|
11
|
+
- '8093'
|
|
12
|
+
- '8094'
|
|
13
|
+
- '8095'
|
|
14
|
+
- '9102'
|
|
15
|
+
- '11210'
|
|
16
16
|
healthcheck: # checks couchbase server is up
|
|
17
|
-
test: [
|
|
17
|
+
test: ['CMD', 'curl', '-v', 'http://localhost:8091/pools']
|
|
18
18
|
interval: 20s
|
|
19
19
|
timeout: 20s
|
|
20
20
|
retries: 5
|
|
21
|
-
container_name: mastra_couchbase_testing
|
|
21
|
+
container_name: mastra_couchbase_testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/couchbase",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-vector-query-sources-20250516172905",
|
|
4
4
|
"description": "Couchbase vector store provider for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"couchbase": "^4.4.5",
|
|
23
|
-
"@mastra/core": "0.0.0-
|
|
23
|
+
"@mastra/core": "0.0.0-vector-query-sources-20250516172905"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@microsoft/api-extractor": "^7.52.1",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"tsup": "^8.4.0",
|
|
33
33
|
"typescript": "^5.8.2",
|
|
34
34
|
"vitest": "^3.0.9",
|
|
35
|
-
"@internal/lint": "0.0.0-
|
|
35
|
+
"@internal/lint": "0.0.0-vector-query-sources-20250516172905"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// The tests will automatically start and configure the required Couchbase container.
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
6
7
|
import axios from 'axios';
|
|
7
8
|
import type { Cluster, Bucket, Scope, Collection } from 'couchbase';
|
|
8
9
|
import { connect } from 'couchbase';
|
|
9
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
10
|
+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
10
11
|
import { CouchbaseVector, DISTANCE_MAPPING } from './index';
|
|
11
12
|
|
|
12
13
|
const containerName = 'mastra_couchbase_testing';
|
|
@@ -172,14 +173,14 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
172
173
|
|
|
173
174
|
describe('Connection', () => {
|
|
174
175
|
it('should connect to couchbase', async () => {
|
|
175
|
-
couchbase_client = new CouchbaseVector(
|
|
176
|
+
couchbase_client = new CouchbaseVector({
|
|
176
177
|
connectionString,
|
|
177
178
|
username,
|
|
178
179
|
password,
|
|
179
|
-
test_bucketName,
|
|
180
|
-
test_scopeName,
|
|
181
|
-
test_collectionName,
|
|
182
|
-
);
|
|
180
|
+
bucketName: test_bucketName,
|
|
181
|
+
scopeName: test_scopeName,
|
|
182
|
+
collectionName: test_collectionName,
|
|
183
|
+
});
|
|
183
184
|
expect(couchbase_client).toBeDefined();
|
|
184
185
|
const collection = await couchbase_client.getCollection();
|
|
185
186
|
expect(collection).toBeDefined();
|
|
@@ -210,14 +211,14 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
210
211
|
}, 50000);
|
|
211
212
|
|
|
212
213
|
it('should describe index', async () => {
|
|
213
|
-
const stats = await couchbase_client.describeIndex(test_indexName);
|
|
214
|
+
const stats = await couchbase_client.describeIndex({ indexName: test_indexName });
|
|
214
215
|
expect(stats.dimension).toBe(dimension);
|
|
215
216
|
expect(stats.metric).toBe('euclidean'); // similiarity(=="l2_norm") is mapped to euclidean in couchbase
|
|
216
217
|
expect(typeof stats.count).toBe('number');
|
|
217
218
|
}, 50000);
|
|
218
219
|
|
|
219
220
|
it('should delete index', async () => {
|
|
220
|
-
await couchbase_client.deleteIndex(test_indexName);
|
|
221
|
+
await couchbase_client.deleteIndex({ indexName: test_indexName });
|
|
221
222
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
222
223
|
await expect(scope.searchIndexes().getIndex(test_indexName)).rejects.toThrowError();
|
|
223
224
|
}, 50000);
|
|
@@ -245,7 +246,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
245
246
|
}, 50000);
|
|
246
247
|
|
|
247
248
|
afterAll(async () => {
|
|
248
|
-
await couchbase_client.deleteIndex(test_indexName);
|
|
249
|
+
await couchbase_client.deleteIndex({ indexName: test_indexName });
|
|
249
250
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
250
251
|
}, 50000);
|
|
251
252
|
|
|
@@ -379,6 +380,120 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
379
380
|
}),
|
|
380
381
|
).rejects.toThrow('Including vectors in search results is not yet supported by the Couchbase vector store');
|
|
381
382
|
}, 50000);
|
|
383
|
+
|
|
384
|
+
it('should upsert vectors with generated ids', async () => {
|
|
385
|
+
const ids = await couchbase_client.upsert({ indexName: test_indexName, vectors: testVectors });
|
|
386
|
+
expect(ids).toHaveLength(testVectors.length);
|
|
387
|
+
ids.forEach(id => expect(typeof id).toBe('string'));
|
|
388
|
+
|
|
389
|
+
// Count is not supported by Couchbase
|
|
390
|
+
const stats = await couchbase_client.describeIndex({ indexName: test_indexName });
|
|
391
|
+
expect(stats.count).toBe(-1);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should update existing vectors', async () => {
|
|
395
|
+
// Initial upsert
|
|
396
|
+
await couchbase_client.upsert({
|
|
397
|
+
indexName: test_indexName,
|
|
398
|
+
vectors: testVectors,
|
|
399
|
+
metadata: testMetadata,
|
|
400
|
+
ids: testVectorIds,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Update first vector
|
|
404
|
+
const updatedVector = [[0.5, 0.5, 0.0]];
|
|
405
|
+
const updatedMetadata = [{ label: 'updated-x-axis' }];
|
|
406
|
+
await couchbase_client.upsert({
|
|
407
|
+
indexName: test_indexName,
|
|
408
|
+
vectors: updatedVector,
|
|
409
|
+
metadata: updatedMetadata,
|
|
410
|
+
ids: [testVectorIds?.[0]!],
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Verify update
|
|
414
|
+
const result = await collection.get(testVectorIds?.[0]!);
|
|
415
|
+
expect(result.content.embedding).toEqual(updatedVector[0]);
|
|
416
|
+
expect(result.content.metadata).toEqual(updatedMetadata[0]);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should update the vector by id', async () => {
|
|
420
|
+
const ids = await couchbase_client.upsert({ indexName: test_indexName, vectors: testVectors });
|
|
421
|
+
expect(ids).toHaveLength(3);
|
|
422
|
+
|
|
423
|
+
const idToBeUpdated = ids[0];
|
|
424
|
+
const newVector = [1, 2, 3];
|
|
425
|
+
const newMetaData = {
|
|
426
|
+
test: 'updates',
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const update = {
|
|
430
|
+
vector: newVector,
|
|
431
|
+
metadata: newMetaData,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
await couchbase_client.updateVector({ indexName: test_indexName, id: idToBeUpdated, update });
|
|
435
|
+
|
|
436
|
+
const result = await collection.get(idToBeUpdated);
|
|
437
|
+
expect(result.content.embedding).toEqual(newVector);
|
|
438
|
+
expect(result.content.metadata).toEqual(newMetaData);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('should only update the metadata by id', async () => {
|
|
442
|
+
const ids = await couchbase_client.upsert({ indexName: test_indexName, vectors: testVectors });
|
|
443
|
+
expect(ids).toHaveLength(3);
|
|
444
|
+
|
|
445
|
+
const idToBeUpdated = ids[0];
|
|
446
|
+
const newMetaData = {
|
|
447
|
+
test: 'updates',
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const update = {
|
|
451
|
+
metadata: newMetaData,
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
await couchbase_client.updateVector({ indexName: test_indexName, id: idToBeUpdated, update });
|
|
455
|
+
|
|
456
|
+
const result = await collection.get(idToBeUpdated);
|
|
457
|
+
expect(result.content.embedding).toEqual(testVectors[0]);
|
|
458
|
+
expect(result.content.metadata).toEqual(newMetaData);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should only update vector embeddings by id', async () => {
|
|
462
|
+
const ids = await couchbase_client.upsert({ indexName: test_indexName, vectors: testVectors });
|
|
463
|
+
expect(ids).toHaveLength(3);
|
|
464
|
+
|
|
465
|
+
const idToBeUpdated = ids[0];
|
|
466
|
+
const newVector = [1, 2, 3];
|
|
467
|
+
|
|
468
|
+
const update = {
|
|
469
|
+
vector: newVector,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
await couchbase_client.updateVector({ indexName: test_indexName, id: idToBeUpdated, update });
|
|
473
|
+
|
|
474
|
+
const result = await collection.get(idToBeUpdated);
|
|
475
|
+
expect(result.content.embedding).toEqual(newVector);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should throw exception when no updates are given', async () => {
|
|
479
|
+
await expect(couchbase_client.updateVector({ indexName: test_indexName, id: 'id', update: {} })).rejects.toThrow(
|
|
480
|
+
'No updates provided',
|
|
481
|
+
);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should delete the vector by id', async () => {
|
|
485
|
+
const ids = await couchbase_client.upsert({ indexName: test_indexName, vectors: testVectors });
|
|
486
|
+
expect(ids).toHaveLength(3);
|
|
487
|
+
const idToBeDeleted = ids[0];
|
|
488
|
+
|
|
489
|
+
await couchbase_client.deleteVector({ indexName: test_indexName, id: idToBeDeleted });
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
await collection.get(idToBeDeleted);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
expect(error).toBeInstanceOf(Error);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
382
497
|
});
|
|
383
498
|
|
|
384
499
|
describe('Error Cases and Edge Cases', () => {
|
|
@@ -408,7 +523,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
408
523
|
expect(allIndexes.find(idx => idx.name === nonExistentIndex)).toBeUndefined();
|
|
409
524
|
|
|
410
525
|
// Now test the couchbase_client method
|
|
411
|
-
await expect(couchbase_client.describeIndex(nonExistentIndex)).rejects.toThrow();
|
|
526
|
+
await expect(couchbase_client.describeIndex({ indexName: nonExistentIndex })).rejects.toThrow();
|
|
412
527
|
}, 50000);
|
|
413
528
|
|
|
414
529
|
it('should throw error when deleting a non-existent index', async () => {
|
|
@@ -419,7 +534,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
419
534
|
expect(allIndexes.find(idx => idx.name === nonExistentIndex)).toBeUndefined();
|
|
420
535
|
|
|
421
536
|
// Now test the couchbase_client method
|
|
422
|
-
await expect(couchbase_client.deleteIndex(nonExistentIndex)).rejects.toThrow();
|
|
537
|
+
await expect(couchbase_client.deleteIndex({ indexName: nonExistentIndex })).rejects.toThrow();
|
|
423
538
|
}, 50000);
|
|
424
539
|
|
|
425
540
|
it('should throw error for empty vectors array in upsert', async () => {
|
|
@@ -431,6 +546,66 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
431
546
|
}),
|
|
432
547
|
).rejects.toThrow('No vectors provided');
|
|
433
548
|
}, 50000);
|
|
549
|
+
|
|
550
|
+
it('should handle non-existent index queries', async () => {
|
|
551
|
+
await expect(
|
|
552
|
+
couchbase_client.query({ indexName: 'non-existent-index', queryVector: [1, 2, 3] }),
|
|
553
|
+
).rejects.toThrow();
|
|
554
|
+
}, 50000);
|
|
555
|
+
|
|
556
|
+
it('should handle duplicate index creation gracefully', async () => {
|
|
557
|
+
const duplicateIndexName = `duplicate-test-${randomUUID()}`;
|
|
558
|
+
const dimension = 768;
|
|
559
|
+
const infoSpy = vi.spyOn(couchbase_client['logger'], 'info');
|
|
560
|
+
const warnSpy = vi.spyOn(couchbase_client['logger'], 'warn');
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
// Create index first time
|
|
564
|
+
await couchbase_client.createIndex({
|
|
565
|
+
indexName: duplicateIndexName,
|
|
566
|
+
dimension,
|
|
567
|
+
metric: 'cosine',
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Try to create with same dimensions - should not throw
|
|
571
|
+
await expect(
|
|
572
|
+
couchbase_client.createIndex({
|
|
573
|
+
indexName: duplicateIndexName,
|
|
574
|
+
dimension,
|
|
575
|
+
metric: 'cosine',
|
|
576
|
+
}),
|
|
577
|
+
).resolves.not.toThrow();
|
|
578
|
+
|
|
579
|
+
expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('already exists with'));
|
|
580
|
+
|
|
581
|
+
// Try to create with same dimensions and different metric - should not throw
|
|
582
|
+
await expect(
|
|
583
|
+
couchbase_client.createIndex({
|
|
584
|
+
indexName: duplicateIndexName,
|
|
585
|
+
dimension,
|
|
586
|
+
metric: 'euclidean',
|
|
587
|
+
}),
|
|
588
|
+
).resolves.not.toThrow();
|
|
589
|
+
|
|
590
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Attempted to create index with metric'));
|
|
591
|
+
|
|
592
|
+
// Try to create with different dimensions - should throw
|
|
593
|
+
await expect(
|
|
594
|
+
couchbase_client.createIndex({
|
|
595
|
+
indexName: duplicateIndexName,
|
|
596
|
+
dimension: dimension + 1,
|
|
597
|
+
metric: 'cosine',
|
|
598
|
+
}),
|
|
599
|
+
).rejects.toThrow(
|
|
600
|
+
`Index "${duplicateIndexName}" already exists with ${dimension} dimensions, but ${dimension + 1} dimensions were requested`,
|
|
601
|
+
);
|
|
602
|
+
} finally {
|
|
603
|
+
infoSpy.mockRestore();
|
|
604
|
+
warnSpy.mockRestore();
|
|
605
|
+
// Cleanup
|
|
606
|
+
await couchbase_client.deleteIndex({ indexName: duplicateIndexName });
|
|
607
|
+
}
|
|
608
|
+
}, 50000);
|
|
434
609
|
});
|
|
435
610
|
|
|
436
611
|
describe('Vector Dimension Tracking', () => {
|
|
@@ -438,7 +613,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
438
613
|
const indexes = await couchbase_client.listIndexes();
|
|
439
614
|
if (indexes.length > 0) {
|
|
440
615
|
for (const index of indexes) {
|
|
441
|
-
await couchbase_client.deleteIndex(index);
|
|
616
|
+
await couchbase_client.deleteIndex({ indexName: index });
|
|
442
617
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
443
618
|
}
|
|
444
619
|
}
|
|
@@ -501,7 +676,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
501
676
|
expect((couchbase_client as any).vector_dimension).toBe(testDimension);
|
|
502
677
|
|
|
503
678
|
// Delete the index
|
|
504
|
-
await couchbase_client.deleteIndex(testIndexName);
|
|
679
|
+
await couchbase_client.deleteIndex({ indexName: testIndexName });
|
|
505
680
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
506
681
|
|
|
507
682
|
// Verify dimension is reset
|
|
@@ -517,7 +692,7 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
517
692
|
const indexes = await couchbase_client.listIndexes();
|
|
518
693
|
if (indexes.length > 0) {
|
|
519
694
|
for (const index of indexes) {
|
|
520
|
-
await couchbase_client.deleteIndex(index);
|
|
695
|
+
await couchbase_client.deleteIndex({ indexName: index });
|
|
521
696
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
522
697
|
}
|
|
523
698
|
}
|
|
@@ -546,11 +721,11 @@ describe('Integration Testing CouchbaseVector', async () => {
|
|
|
546
721
|
expect(similarityParam).toBe(couchbaseMetric);
|
|
547
722
|
|
|
548
723
|
// Verify through our API
|
|
549
|
-
const stats = await couchbase_client.describeIndex(testIndexName);
|
|
724
|
+
const stats = await couchbase_client.describeIndex({ indexName: testIndexName });
|
|
550
725
|
expect(stats.metric).toBe(mastraMetric);
|
|
551
726
|
|
|
552
727
|
// Clean up
|
|
553
|
-
await couchbase_client.deleteIndex(testIndexName);
|
|
728
|
+
await couchbase_client.deleteIndex({ indexName: testIndexName });
|
|
554
729
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
555
730
|
}
|
|
556
731
|
}, 50000);
|