@mastra/couchbase 0.0.0-vnext-inngest-20250508131921 → 0.0.0-vnext-20251119160359

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.
@@ -1,287 +0,0 @@
1
- import { MastraVector } from '@mastra/core/vector';
2
- import type {
3
- QueryResult,
4
- IndexStats,
5
- CreateIndexParams,
6
- UpsertVectorParams,
7
- QueryVectorParams,
8
- } from '@mastra/core/vector';
9
- import type { Bucket, Cluster, Collection, Scope } from 'couchbase';
10
- import { connect, SearchRequest, VectorQuery, VectorSearch } from 'couchbase';
11
-
12
- type MastraMetric = 'cosine' | 'euclidean' | 'dotproduct';
13
- type CouchbaseMetric = 'cosine' | 'l2_norm' | 'dot_product';
14
- export const DISTANCE_MAPPING: Record<MastraMetric, CouchbaseMetric> = {
15
- cosine: 'cosine',
16
- euclidean: 'l2_norm',
17
- dotproduct: 'dot_product',
18
- };
19
-
20
- export class CouchbaseVector extends MastraVector {
21
- private clusterPromise: Promise<Cluster>;
22
- private cluster: Cluster;
23
- private bucketName: string;
24
- private collectionName: string;
25
- private scopeName: string;
26
- private collection: Collection;
27
- private bucket: Bucket;
28
- private scope: Scope;
29
- private vector_dimension: number;
30
-
31
- constructor(
32
- cnn_string: string,
33
- username: string,
34
- password: string,
35
- bucketName: string,
36
- scopeName: string,
37
- collectionName: string,
38
- ) {
39
- super();
40
-
41
- const baseClusterPromise = connect(cnn_string, {
42
- username,
43
- password,
44
- configProfile: 'wanDevelopment',
45
- });
46
-
47
- const telemetry = this.__getTelemetry();
48
- this.clusterPromise =
49
- telemetry?.traceClass(baseClusterPromise, {
50
- spanNamePrefix: 'couchbase-vector',
51
- attributes: {
52
- 'vector.type': 'couchbase',
53
- },
54
- }) ?? baseClusterPromise;
55
- this.cluster = null as unknown as Cluster;
56
- this.bucketName = bucketName;
57
- this.collectionName = collectionName;
58
- this.scopeName = scopeName;
59
- this.collection = null as unknown as Collection;
60
- this.bucket = null as unknown as Bucket;
61
- this.scope = null as unknown as Scope;
62
- this.vector_dimension = null as unknown as number;
63
- }
64
-
65
- async getCollection() {
66
- if (!this.cluster) {
67
- this.cluster = await this.clusterPromise;
68
- }
69
-
70
- if (!this.collection) {
71
- this.bucket = this.cluster.bucket(this.bucketName);
72
- this.scope = this.bucket.scope(this.scopeName);
73
- this.collection = this.scope.collection(this.collectionName);
74
- }
75
-
76
- return this.collection;
77
- }
78
-
79
- async createIndex(params: CreateIndexParams): Promise<void> {
80
- const { indexName, dimension, metric = 'dotproduct' as MastraMetric } = params;
81
- await this.getCollection();
82
-
83
- if (!Number.isInteger(dimension) || dimension <= 0) {
84
- throw new Error('Dimension must be a positive integer');
85
- }
86
-
87
- try {
88
- await this.scope.searchIndexes().upsertIndex({
89
- name: indexName,
90
- sourceName: this.bucketName,
91
- type: 'fulltext-index',
92
- params: {
93
- doc_config: {
94
- docid_prefix_delim: '',
95
- docid_regexp: '',
96
- mode: 'scope.collection.type_field',
97
- type_field: 'type',
98
- },
99
- mapping: {
100
- default_analyzer: 'standard',
101
- default_datetime_parser: 'dateTimeOptional',
102
- default_field: '_all',
103
- default_mapping: {
104
- dynamic: true,
105
- enabled: false,
106
- },
107
- default_type: '_default',
108
- docvalues_dynamic: true, // [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
109
- index_dynamic: true,
110
- store_dynamic: true, // [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
111
- type_field: '_type',
112
- types: {
113
- [`${this.scopeName}.${this.collectionName}`]: {
114
- dynamic: true,
115
- enabled: true,
116
- properties: {
117
- embedding: {
118
- enabled: true,
119
- fields: [
120
- {
121
- dims: dimension,
122
- index: true,
123
- name: 'embedding',
124
- similarity: DISTANCE_MAPPING[metric],
125
- type: 'vector',
126
- vector_index_optimized_for: 'recall',
127
- store: true, // CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
128
- docvalues: true, // CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
129
- include_term_vectors: true, // CHANGED due to https://docs.couchbase.com/server/current/search/search-index-params.html#fields
130
- },
131
- ],
132
- },
133
- content: {
134
- enabled: true,
135
- fields: [
136
- {
137
- index: true,
138
- name: 'content',
139
- store: true,
140
- type: 'text',
141
- },
142
- ],
143
- },
144
- },
145
- },
146
- },
147
- },
148
- store: {
149
- indexType: 'scorch',
150
- segmentVersion: 16,
151
- },
152
- },
153
- sourceUuid: '',
154
- sourceParams: {},
155
- sourceType: 'gocbcore',
156
- planParams: {
157
- maxPartitionsPerPIndex: 64,
158
- indexPartitions: 16,
159
- numReplicas: 0,
160
- },
161
- });
162
- this.vector_dimension = dimension;
163
- } catch (error: any) {
164
- // Check for 'already exists' error (Couchbase may throw a 400 or 409, or have a message)
165
- const message = error?.message || error?.toString();
166
- if (message && message.toLowerCase().includes('index exists')) {
167
- // Fetch index info and check dimension
168
- await this.validateExistingIndex(indexName, dimension, metric);
169
- return;
170
- }
171
- throw error;
172
- }
173
- }
174
-
175
- async upsert(params: UpsertVectorParams): Promise<string[]> {
176
- const { vectors, metadata, ids } = params;
177
- await this.getCollection();
178
-
179
- if (!vectors || vectors.length === 0) {
180
- throw new Error('No vectors provided');
181
- }
182
- if (this.vector_dimension) {
183
- for (const vector of vectors) {
184
- if (!vector || this.vector_dimension !== vector.length) {
185
- throw new Error('Vector dimension mismatch');
186
- }
187
- }
188
- }
189
-
190
- const pointIds = ids || vectors.map(() => crypto.randomUUID());
191
- const records = vectors.map((vector, i) => {
192
- const metadataObj = metadata?.[i] || {};
193
- const record: Record<string, any> = {
194
- embedding: vector,
195
- metadata: metadataObj,
196
- };
197
- // If metadata has a text field, save it as content
198
- if (metadataObj.text) {
199
- record.content = metadataObj.text;
200
- }
201
- return record;
202
- });
203
-
204
- const allPromises = [];
205
- for (let i = 0; i < records.length; i++) {
206
- allPromises.push(this.collection.upsert(pointIds[i]!, records[i]));
207
- }
208
- await Promise.all(allPromises);
209
-
210
- return pointIds;
211
- }
212
-
213
- async query(params: QueryVectorParams): Promise<QueryResult[]> {
214
- const { indexName, queryVector, topK = 10, includeVector = false } = params;
215
-
216
- await this.getCollection();
217
-
218
- const index_stats = await this.describeIndex(indexName);
219
- if (queryVector.length !== index_stats.dimension) {
220
- throw new Error(`Query vector dimension mismatch. Expected ${index_stats.dimension}, got ${queryVector.length}`);
221
- }
222
-
223
- let request = SearchRequest.create(
224
- VectorSearch.fromVectorQuery(VectorQuery.create('embedding', queryVector).numCandidates(topK)),
225
- );
226
- const results = await this.scope.search(indexName, request, {
227
- fields: ['*'],
228
- });
229
-
230
- if (includeVector) {
231
- throw new Error('Including vectors in search results is not yet supported by the Couchbase vector store');
232
- }
233
- const output = [];
234
- for (const match of results.rows) {
235
- const cleanedMetadata: Record<string, any> = {};
236
- const fields = (match.fields as Record<string, any>) || {}; // Ensure fields is an object
237
- for (const key in fields) {
238
- if (Object.prototype.hasOwnProperty.call(fields, key)) {
239
- const newKey = key.startsWith('metadata.') ? key.substring('metadata.'.length) : key;
240
- cleanedMetadata[newKey] = fields[key];
241
- }
242
- }
243
- output.push({
244
- id: match.id as string,
245
- score: (match.score as number) || 0,
246
- metadata: cleanedMetadata, // Use the cleaned metadata object
247
- });
248
- }
249
- return output;
250
- }
251
-
252
- async listIndexes(): Promise<string[]> {
253
- await this.getCollection();
254
- const indexes = await this.scope.searchIndexes().getAllIndexes();
255
- return indexes?.map(index => index.name) || [];
256
- }
257
-
258
- async describeIndex(indexName: string): Promise<IndexStats> {
259
- await this.getCollection();
260
- if (!(await this.listIndexes()).includes(indexName)) {
261
- throw new Error(`Index ${indexName} does not exist`);
262
- }
263
- const index = await this.scope.searchIndexes().getIndex(indexName);
264
- const dimensions =
265
- index.params.mapping?.types?.[`${this.scopeName}.${this.collectionName}`]?.properties?.embedding?.fields?.[0]
266
- ?.dims;
267
- const count = -1; // Not added support yet for adding a count of documents covered by an index
268
- const metric = index.params.mapping?.types?.[`${this.scopeName}.${this.collectionName}`]?.properties?.embedding
269
- ?.fields?.[0]?.similarity as CouchbaseMetric;
270
- return {
271
- dimension: dimensions,
272
- count: count,
273
- metric: Object.keys(DISTANCE_MAPPING).find(
274
- key => DISTANCE_MAPPING[key as MastraMetric] === metric,
275
- ) as MastraMetric,
276
- };
277
- }
278
-
279
- async deleteIndex(indexName: string): Promise<void> {
280
- await this.getCollection();
281
- if (!(await this.listIndexes()).includes(indexName)) {
282
- throw new Error(`Index ${indexName} does not exist`);
283
- }
284
- await this.scope.searchIndexes().dropIndex(indexName);
285
- this.vector_dimension = null as unknown as number;
286
- }
287
- }