@mastra/weaviate 0.0.0-alpha.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.
package/dist/index.js ADDED
@@ -0,0 +1,806 @@
1
+ import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
2
+ import { createVectorErrorId } from "@mastra/core/storage";
3
+ import { MastraVector, validateTopK, validateUpsertInput, validateVectorValues } from "@mastra/core/vector";
4
+ import weaviate, { Filters, generateUuid5 } from "weaviate-client";
5
+ import { BaseFilterTranslator } from "@mastra/core/vector/filter";
6
+ //#region src/vector/encoding.ts
7
+ /**
8
+ * Weaviate reserves certain property names (e.g. `id`, `vector`). Metadata keys
9
+ * that collide are stored under a stable prefix and restored on read. This
10
+ * module is the single source of truth shared by the adapter and the filter
11
+ * translator — they must encode keys identically or filters will silently miss.
12
+ */
13
+ /** Internal property used to round-trip the caller's original id. */
14
+ const MASTRA_ID_PROPERTY = "mastraId";
15
+ const META_KEY_PREFIX = "mastraMeta_";
16
+ const RESERVED_META_KEYS = /* @__PURE__ */ new Set([
17
+ "id",
18
+ "vector",
19
+ "_additional",
20
+ MASTRA_ID_PROPERTY
21
+ ]);
22
+ function encodeMetaKey(key) {
23
+ return RESERVED_META_KEYS.has(key) || key.startsWith("mastraMeta_") ? `${META_KEY_PREFIX}${key}` : key;
24
+ }
25
+ function decodeMetaKey(key) {
26
+ return key.startsWith("mastraMeta_") ? key.slice(11) : key;
27
+ }
28
+ function encodeMetaProperties(metadata) {
29
+ if (!metadata) return {};
30
+ const out = {};
31
+ for (const [key, value] of Object.entries(metadata)) out[encodeMetaKey(key)] = value;
32
+ return out;
33
+ }
34
+ //#endregion
35
+ //#region src/vector/filter.ts
36
+ /**
37
+ * Translates MongoDB-style filters into Weaviate `FilterValue` objects.
38
+ *
39
+ * Weaviate filters are built from the collection's `filter` API (which produces
40
+ * protobuf-backed targets), so `translate` requires the collection filter API to
41
+ * be supplied alongside the filter.
42
+ */
43
+ var WeaviateFilterTranslator = class extends BaseFilterTranslator {
44
+ getSupportedOperators() {
45
+ return {
46
+ logical: [
47
+ "$and",
48
+ "$or",
49
+ "$not"
50
+ ],
51
+ basic: ["$eq", "$ne"],
52
+ numeric: [
53
+ "$gt",
54
+ "$gte",
55
+ "$lt",
56
+ "$lte"
57
+ ],
58
+ array: [
59
+ "$in",
60
+ "$nin",
61
+ "$all"
62
+ ],
63
+ element: ["$exists"],
64
+ regex: [],
65
+ custom: []
66
+ };
67
+ }
68
+ isOperator(key) {
69
+ return super.isOperator(key) || key === "$not";
70
+ }
71
+ translate(filter, api) {
72
+ if (this.isEmpty(filter)) return void 0;
73
+ if (!api) throw new Error("WeaviateFilterTranslator.translate requires a collection filter API");
74
+ this.validateFilter(filter);
75
+ return this.translateNode(filter, api);
76
+ }
77
+ combine(parts) {
78
+ const filtered = parts.filter((p) => p !== void 0);
79
+ if (filtered.length === 0) return void 0;
80
+ if (filtered.length === 1) return filtered[0];
81
+ return Filters.and(...filtered);
82
+ }
83
+ translateNode(node, api) {
84
+ const parts = Object.entries(node).map(([key, value]) => {
85
+ if (key === "$and") return this.combineLogical("and", value, api);
86
+ if (key === "$or") return this.combineLogical("or", value, api);
87
+ if (key === "$not") return this.negateNode(value, api);
88
+ return this.translateField(key, value, api);
89
+ });
90
+ return this.combine(parts);
91
+ }
92
+ /** Combines parts with OR, dropping undefined entries. */
93
+ combineOr(parts) {
94
+ const filtered = parts.filter((p) => p !== void 0);
95
+ if (filtered.length === 0) return void 0;
96
+ if (filtered.length === 1) return filtered[0];
97
+ return Filters.or(...filtered);
98
+ }
99
+ /**
100
+ * Returns the negation of a node. A node's entries are AND-combined, so the
101
+ * negation is the OR of each entry's negation (De Morgan).
102
+ */
103
+ negateNode(node, api) {
104
+ const parts = Object.entries(node).map(([key, value]) => {
105
+ if (key === "$and") return this.combineOr(value.map((v) => this.negateNode(v, api)));
106
+ if (key === "$or") return this.combine(value.map((v) => this.negateNode(v, api)));
107
+ if (key === "$not") return this.translateNode(value, api);
108
+ return this.negateField(key, value, api);
109
+ });
110
+ return this.combineOr(parts);
111
+ }
112
+ /** Returns the negation of a single field condition. */
113
+ negateField(field, value, api) {
114
+ const prop = api.byProperty(encodeMetaKey(field));
115
+ if (value === null) return prop.isNull(false);
116
+ if (this.isPrimitive(value)) return prop.notEqual(this.normalizeComparisonValue(value));
117
+ if (Array.isArray(value)) return this.combine(this.normalizeArrayValues(value).map((v) => prop.notEqual(v)));
118
+ const parts = Object.entries(value).map(([op, opValue]) => this.negateSingleOp(field, op, opValue, api));
119
+ return this.combineOr(parts);
120
+ }
121
+ negateSingleOp(field, op, value, api) {
122
+ const prop = api.byProperty(encodeMetaKey(field));
123
+ switch (op) {
124
+ case "$eq": return value === null ? prop.isNull(false) : prop.notEqual(this.normalizeComparisonValue(value));
125
+ case "$ne": return value === null ? prop.isNull(true) : prop.equal(this.normalizeComparisonValue(value));
126
+ case "$gt": return prop.lessOrEqual(this.normalizeComparisonValue(value));
127
+ case "$gte": return prop.lessThan(this.normalizeComparisonValue(value));
128
+ case "$lt": return prop.greaterOrEqual(this.normalizeComparisonValue(value));
129
+ case "$lte": return prop.greaterThan(this.normalizeComparisonValue(value));
130
+ case "$in": return this.combine(this.normalizeArrayValues(value).map((v) => prop.notEqual(v)));
131
+ case "$nin": return prop.containsAny(this.normalizeArrayValues(value));
132
+ case "$exists": return prop.isNull(!!value);
133
+ case "$not": return this.translateField(field, value, api);
134
+ default: throw unsupportedOperatorError(op, field);
135
+ }
136
+ }
137
+ combineLogical(op, values, api) {
138
+ const translated = values.map((v) => this.translateNode(v, api)).filter((p) => p !== void 0);
139
+ if (translated.length === 0) return void 0;
140
+ if (translated.length === 1) return translated[0];
141
+ return op === "and" ? Filters.and(...translated) : Filters.or(...translated);
142
+ }
143
+ translateField(field, value, api) {
144
+ const prop = api.byProperty(encodeMetaKey(field));
145
+ if (value === null) return prop.isNull(true);
146
+ if (this.isPrimitive(value)) return prop.equal(this.normalizeComparisonValue(value));
147
+ if (Array.isArray(value)) return prop.containsAny(this.normalizeArrayValues(value));
148
+ const parts = Object.entries(value).map(([op, opValue]) => this.translateOperator(field, op, opValue, api));
149
+ return this.combine(parts);
150
+ }
151
+ translateOperator(field, op, value, api) {
152
+ const prop = api.byProperty(encodeMetaKey(field));
153
+ switch (op) {
154
+ case "$eq": return value === null ? prop.isNull(true) : prop.equal(this.normalizeComparisonValue(value));
155
+ case "$ne": return value === null ? prop.isNull(false) : prop.notEqual(this.normalizeComparisonValue(value));
156
+ case "$gt": return prop.greaterThan(this.normalizeComparisonValue(value));
157
+ case "$gte": return prop.greaterOrEqual(this.normalizeComparisonValue(value));
158
+ case "$lt": return prop.lessThan(this.normalizeComparisonValue(value));
159
+ case "$lte": return prop.lessOrEqual(this.normalizeComparisonValue(value));
160
+ case "$in": return prop.containsAny(this.normalizeArrayValues(value));
161
+ case "$nin": return this.combine(this.normalizeArrayValues(value).map((v) => prop.notEqual(v)));
162
+ case "$all": return prop.containsAll(this.normalizeArrayValues(value));
163
+ case "$exists": return prop.isNull(!value);
164
+ case "$not": return this.negateField(field, value, api);
165
+ default: throw unsupportedOperatorError(op, field);
166
+ }
167
+ }
168
+ };
169
+ /** Builds a user-facing error for an operator Weaviate cannot translate. */
170
+ function unsupportedOperatorError(op, field) {
171
+ return new MastraError({
172
+ id: "STORAGE_WEAVIATE_FILTER_UNSUPPORTED_OPERATOR",
173
+ text: `Unsupported filter operator "${op}" on field "${field}"`,
174
+ domain: ErrorDomain.STORAGE,
175
+ category: ErrorCategory.USER,
176
+ details: {
177
+ operator: op,
178
+ field
179
+ }
180
+ });
181
+ }
182
+ //#endregion
183
+ //#region src/vector/index.ts
184
+ /**
185
+ * Weaviate distance metrics mapped from Mastra metric names.
186
+ * @see https://weaviate.io/developers/weaviate/config-refs/distances
187
+ */
188
+ const DISTANCE_MAPPING = {
189
+ cosine: "cosine",
190
+ euclidean: "l2-squared",
191
+ dotproduct: "dot"
192
+ };
193
+ const REVERSE_DISTANCE_MAPPING = {
194
+ cosine: "cosine",
195
+ "l2-squared": "euclidean",
196
+ dot: "dotproduct"
197
+ };
198
+ /**
199
+ * A Mastra vector store backed by Weaviate.
200
+ *
201
+ * Vectors are stored in `vectorizer: none` collections (Mastra supplies the
202
+ * embeddings). Because Weaviate requires UUID object ids and capitalised
203
+ * collection names, this adapter maps arbitrary Mastra ids to deterministic
204
+ * UUIDv5 values (preserving the original id in a reserved property) and
205
+ * preserves the original index name in the collection description.
206
+ */
207
+ var WeaviateVector = class extends MastraVector {
208
+ clientPromise = null;
209
+ connectOptions;
210
+ /** Cache of property names known to exist in a collection's schema. */
211
+ knownProperties = /* @__PURE__ */ new Map();
212
+ /** Serializes schema mutations per collection to avoid concurrent addProperty races. */
213
+ schemaLocks = /* @__PURE__ */ new Map();
214
+ constructor(params) {
215
+ super({ id: params.id });
216
+ this.connectOptions = params;
217
+ }
218
+ getClient() {
219
+ if (!this.clientPromise) {
220
+ const { httpHost, httpPort, httpSecure, grpcHost, grpcPort, grpcSecure, apiKey, headers } = this.connectOptions;
221
+ this.clientPromise = weaviate.connectToCustom({
222
+ httpHost: httpHost ?? "localhost",
223
+ httpPort: httpPort ?? 8080,
224
+ httpSecure: httpSecure ?? false,
225
+ grpcHost: grpcHost ?? httpHost ?? "localhost",
226
+ grpcPort: grpcPort ?? 50051,
227
+ grpcSecure: grpcSecure ?? false,
228
+ ...apiKey ? { authCredentials: new weaviate.ApiKey(apiKey) } : {},
229
+ ...headers ? { headers } : {}
230
+ });
231
+ }
232
+ return this.clientPromise;
233
+ }
234
+ /** Closes the underlying Weaviate connection. */
235
+ async disconnect() {
236
+ if (this.clientPromise) {
237
+ await (await this.clientPromise).close();
238
+ this.clientPromise = null;
239
+ }
240
+ }
241
+ /** Weaviate collection names must start with an uppercase letter. */
242
+ toCollectionName(indexName) {
243
+ return indexName.charAt(0).toUpperCase() + indexName.slice(1);
244
+ }
245
+ async getCollection(indexName) {
246
+ return (await this.getClient()).collections.get(this.toCollectionName(indexName));
247
+ }
248
+ parseMeta(description) {
249
+ if (!description) return null;
250
+ try {
251
+ const parsed = JSON.parse(description);
252
+ if (parsed && typeof parsed === "object" && typeof parsed.name === "string") return parsed;
253
+ } catch {}
254
+ return null;
255
+ }
256
+ inferDataType(value) {
257
+ if (typeof value === "string") return "text";
258
+ if (typeof value === "number") return "number";
259
+ if (typeof value === "boolean") return "boolean";
260
+ if (Array.isArray(value)) {
261
+ const first = value.find((v) => v !== null && v !== void 0);
262
+ if (typeof first === "string") return "text[]";
263
+ if (typeof first === "number") return "number[]";
264
+ if (typeof first === "boolean") return "boolean[]";
265
+ return null;
266
+ }
267
+ return null;
268
+ }
269
+ /**
270
+ * Ensures every metadata key across the batch exists as a collection property.
271
+ * Weaviate's auto-schema would create text properties with `word` tokenization
272
+ * (breaking exact-match filtering), so properties are created explicitly with
273
+ * `field` tokenization for text values.
274
+ */
275
+ async ensureProperties(collection, collectionName, metadata) {
276
+ if (!metadata?.length) return;
277
+ const next = (this.schemaLocks.get(collectionName) ?? Promise.resolve()).catch(() => void 0).then(() => this.ensurePropertiesLocked(collection, collectionName, metadata));
278
+ this.schemaLocks.set(collectionName, next);
279
+ await next;
280
+ }
281
+ async ensurePropertiesLocked(collection, collectionName, metadata) {
282
+ let known = this.knownProperties.get(collectionName);
283
+ if (!known) {
284
+ const config = await collection.config.get();
285
+ known = new Set(config.properties.map((p) => p.name));
286
+ this.knownProperties.set(collectionName, known);
287
+ }
288
+ const seen = /* @__PURE__ */ new Map();
289
+ for (const row of metadata) {
290
+ if (!row) continue;
291
+ for (const [rawKey, value] of Object.entries(row)) {
292
+ const key = encodeMetaKey(rawKey);
293
+ if (known.has(key) || seen.has(key) || value === null || value === void 0) continue;
294
+ const dataType = this.inferDataType(value);
295
+ if (dataType) seen.set(key, dataType);
296
+ }
297
+ }
298
+ for (const [name, dataType] of seen) {
299
+ try {
300
+ await collection.config.addProperty({
301
+ name,
302
+ dataType,
303
+ ...dataType === "text" || dataType === "text[]" ? { tokenization: "field" } : {}
304
+ });
305
+ } catch (error) {
306
+ if (!/already/i.test(error?.message ?? "")) throw error;
307
+ }
308
+ known.add(name);
309
+ }
310
+ }
311
+ async createIndex({ indexName, dimension, metric = "cosine" }) {
312
+ try {
313
+ if (!Number.isInteger(dimension) || dimension <= 0) throw new MastraError({
314
+ id: createVectorErrorId("WEAVIATE", "CREATE_INDEX", "INVALID_ARGS"),
315
+ text: "Dimension must be a positive integer",
316
+ domain: ErrorDomain.STORAGE,
317
+ category: ErrorCategory.USER,
318
+ details: {
319
+ indexName,
320
+ dimension
321
+ }
322
+ });
323
+ const distance = DISTANCE_MAPPING[metric];
324
+ if (!distance) throw new MastraError({
325
+ id: createVectorErrorId("WEAVIATE", "CREATE_INDEX", "INVALID_ARGS"),
326
+ text: `Invalid metric: ${metric}. Must be one of: cosine, euclidean, dotproduct`,
327
+ domain: ErrorDomain.STORAGE,
328
+ category: ErrorCategory.USER,
329
+ details: {
330
+ indexName,
331
+ metric
332
+ }
333
+ });
334
+ const client = await this.getClient();
335
+ const collectionName = this.toCollectionName(indexName);
336
+ if (await client.collections.exists(collectionName)) {
337
+ const existingConfig = await client.collections.get(collectionName).config.get();
338
+ const existingMeta = this.parseMeta(existingConfig.description);
339
+ if (existingMeta && existingMeta.name !== indexName) throw new MastraError({
340
+ id: createVectorErrorId("WEAVIATE", "CREATE_INDEX", "NAME_COLLISION"),
341
+ text: `Index name "${indexName}" collides with existing index "${existingMeta.name}" (Weaviate collection "${collectionName}"). Weaviate capitalises the first letter of collection names, so these names cannot coexist.`,
342
+ domain: ErrorDomain.STORAGE,
343
+ category: ErrorCategory.USER,
344
+ details: {
345
+ indexName,
346
+ existingName: existingMeta.name,
347
+ collectionName
348
+ }
349
+ });
350
+ await this.validateExistingIndex(indexName, dimension, metric);
351
+ return;
352
+ }
353
+ const meta = {
354
+ name: indexName,
355
+ dimension,
356
+ metric
357
+ };
358
+ await client.collections.create({
359
+ name: collectionName,
360
+ description: JSON.stringify(meta),
361
+ properties: [{
362
+ name: MASTRA_ID_PROPERTY,
363
+ dataType: "text",
364
+ tokenization: "field"
365
+ }],
366
+ invertedIndex: weaviate.configure.invertedIndex({ indexNullState: true }),
367
+ vectorizers: weaviate.configure.vectors.selfProvided({ vectorIndexConfig: weaviate.configure.vectorIndex.hnsw({ distanceMetric: distance }) })
368
+ });
369
+ this.knownProperties.set(collectionName, /* @__PURE__ */ new Set([MASTRA_ID_PROPERTY]));
370
+ } catch (error) {
371
+ if (error instanceof MastraError) throw error;
372
+ throw new MastraError({
373
+ id: createVectorErrorId("WEAVIATE", "CREATE_INDEX", "FAILED"),
374
+ domain: ErrorDomain.STORAGE,
375
+ category: ErrorCategory.THIRD_PARTY,
376
+ details: { indexName }
377
+ }, error);
378
+ }
379
+ }
380
+ /** Fetches a collection's stored metadata, throwing if the index does not exist. */
381
+ async requireMeta(indexName, operation) {
382
+ const client = await this.getClient();
383
+ const collectionName = this.toCollectionName(indexName);
384
+ if (!await client.collections.exists(collectionName)) throw new MastraError({
385
+ id: createVectorErrorId("WEAVIATE", operation, "INDEX_NOT_FOUND"),
386
+ text: `Index ${indexName} does not exist`,
387
+ domain: ErrorDomain.STORAGE,
388
+ category: ErrorCategory.USER,
389
+ details: { indexName }
390
+ });
391
+ const config = await client.collections.get(collectionName).config.get();
392
+ return this.parseMeta(config.description);
393
+ }
394
+ async upsert({ indexName, vectors, metadata, ids }) {
395
+ try {
396
+ validateUpsertInput("WEAVIATE", vectors, metadata, ids);
397
+ validateVectorValues("WEAVIATE", vectors);
398
+ const meta = await this.requireMeta(indexName, "UPSERT");
399
+ if (meta?.dimension) {
400
+ const mismatch = vectors.find((v) => v.length !== meta.dimension);
401
+ if (mismatch) throw new MastraError({
402
+ id: createVectorErrorId("WEAVIATE", "UPSERT", "DIMENSION_MISMATCH"),
403
+ text: `Vector dimension ${mismatch.length} does not match index dimension ${meta.dimension}`,
404
+ domain: ErrorDomain.STORAGE,
405
+ category: ErrorCategory.USER,
406
+ details: {
407
+ indexName,
408
+ expected: meta.dimension,
409
+ actual: mismatch.length
410
+ }
411
+ });
412
+ }
413
+ const collection = await this.getCollection(indexName);
414
+ await this.ensureProperties(collection, this.toCollectionName(indexName), metadata);
415
+ const originalIds = ids ?? vectors.map(() => crypto.randomUUID());
416
+ const objects = vectors.map((vector, i) => {
417
+ const originalId = originalIds[i];
418
+ return {
419
+ id: generateUuid5(originalId),
420
+ vectors: vector,
421
+ properties: {
422
+ ...encodeMetaProperties(metadata?.[i]),
423
+ [MASTRA_ID_PROPERTY]: originalId
424
+ }
425
+ };
426
+ });
427
+ const result = await collection.data.insertMany(objects);
428
+ if (result.hasErrors) {
429
+ const firstError = Object.values(result.errors)[0];
430
+ throw new Error(firstError?.message ?? "Weaviate insertMany reported errors");
431
+ }
432
+ return originalIds;
433
+ } catch (error) {
434
+ if (error instanceof MastraError) throw error;
435
+ throw new MastraError({
436
+ id: createVectorErrorId("WEAVIATE", "UPSERT", "FAILED"),
437
+ domain: ErrorDomain.STORAGE,
438
+ category: ErrorCategory.THIRD_PARTY,
439
+ details: { indexName }
440
+ }, error);
441
+ }
442
+ }
443
+ transformFilter(collection, filter) {
444
+ return new WeaviateFilterTranslator().translate(filter, collection.filter);
445
+ }
446
+ toQueryResult(obj, includeVector) {
447
+ const raw = obj.properties ?? {};
448
+ const originalId = raw[MASTRA_ID_PROPERTY];
449
+ const properties = {};
450
+ for (const [key, value] of Object.entries(raw)) {
451
+ if (key === "mastraId" || value === void 0) continue;
452
+ properties[decodeMetaKey(key)] = value;
453
+ }
454
+ const distance = obj.metadata?.distance;
455
+ const score = typeof distance === "number" ? 1 - distance : obj.metadata?.certainty ?? 0;
456
+ const result = {
457
+ id: typeof originalId === "string" ? originalId : obj.uuid,
458
+ score,
459
+ metadata: properties
460
+ };
461
+ if (includeVector) {
462
+ const vectors = obj.vectors ?? {};
463
+ result.vector = vectors.default ?? Object.values(vectors)[0];
464
+ }
465
+ return result;
466
+ }
467
+ async query({ indexName, queryVector, topK = 10, filter, includeVector = false }) {
468
+ try {
469
+ validateTopK("WEAVIATE", topK);
470
+ const meta = await this.requireMeta(indexName, "QUERY");
471
+ if (queryVector && meta?.dimension && queryVector.length !== meta.dimension) throw new MastraError({
472
+ id: createVectorErrorId("WEAVIATE", "QUERY", "DIMENSION_MISMATCH"),
473
+ text: `Query vector dimension ${queryVector.length} does not match index dimension ${meta.dimension}`,
474
+ domain: ErrorDomain.STORAGE,
475
+ category: ErrorCategory.USER,
476
+ details: {
477
+ indexName,
478
+ expected: meta.dimension,
479
+ actual: queryVector.length
480
+ }
481
+ });
482
+ const collection = await this.getCollection(indexName);
483
+ const filters = this.transformFilter(collection, filter);
484
+ const returnMetadata = ["distance"];
485
+ if (queryVector) return (await collection.query.nearVector(queryVector, {
486
+ limit: topK,
487
+ includeVector,
488
+ returnMetadata,
489
+ ...filters ? { filters } : {}
490
+ })).objects.map((obj) => this.toQueryResult(obj, includeVector));
491
+ return (await collection.query.fetchObjects({
492
+ limit: topK,
493
+ includeVector,
494
+ ...filters ? { filters } : {}
495
+ })).objects.map((obj) => this.toQueryResult(obj, includeVector));
496
+ } catch (error) {
497
+ if (error instanceof MastraError) throw error;
498
+ throw new MastraError({
499
+ id: createVectorErrorId("WEAVIATE", "QUERY", "FAILED"),
500
+ domain: ErrorDomain.STORAGE,
501
+ category: ErrorCategory.THIRD_PARTY,
502
+ details: { indexName }
503
+ }, error);
504
+ }
505
+ }
506
+ async listIndexes() {
507
+ try {
508
+ return (await (await this.getClient()).collections.listAll()).map((config) => {
509
+ return this.parseMeta(config.description)?.name ?? config.name;
510
+ });
511
+ } catch (error) {
512
+ throw new MastraError({
513
+ id: createVectorErrorId("WEAVIATE", "LIST_INDEXES", "FAILED"),
514
+ domain: ErrorDomain.STORAGE,
515
+ category: ErrorCategory.THIRD_PARTY
516
+ }, error);
517
+ }
518
+ }
519
+ async describeIndex({ indexName }) {
520
+ try {
521
+ const client = await this.getClient();
522
+ const collectionName = this.toCollectionName(indexName);
523
+ if (!await client.collections.exists(collectionName)) throw new MastraError({
524
+ id: createVectorErrorId("WEAVIATE", "DESCRIBE_INDEX", "INDEX_NOT_FOUND"),
525
+ text: `Index ${indexName} does not exist`,
526
+ domain: ErrorDomain.STORAGE,
527
+ category: ErrorCategory.USER,
528
+ details: { indexName }
529
+ });
530
+ const collection = client.collections.get(collectionName);
531
+ const config = await collection.config.get();
532
+ const meta = this.parseMeta(config.description);
533
+ const count = await collection.length();
534
+ let dimension = meta?.dimension ?? 0;
535
+ if (!dimension) {
536
+ const vectors = (await collection.query.fetchObjects({
537
+ limit: 1,
538
+ includeVector: true
539
+ })).objects[0]?.vectors ?? {};
540
+ const vec = vectors.default ?? Object.values(vectors)[0];
541
+ if (Array.isArray(vec)) dimension = vec.length;
542
+ }
543
+ let metric = meta?.metric;
544
+ if (!metric) {
545
+ const distance = (config.vectorizers?.default ?? Object.values(config.vectorizers ?? {})[0])?.indexConfig?.distance;
546
+ metric = distance ? REVERSE_DISTANCE_MAPPING[distance] : void 0;
547
+ }
548
+ return {
549
+ dimension,
550
+ count,
551
+ metric
552
+ };
553
+ } catch (error) {
554
+ if (error instanceof MastraError) throw error;
555
+ throw new MastraError({
556
+ id: createVectorErrorId("WEAVIATE", "DESCRIBE_INDEX", "FAILED"),
557
+ domain: ErrorDomain.STORAGE,
558
+ category: ErrorCategory.THIRD_PARTY,
559
+ details: { indexName }
560
+ }, error);
561
+ }
562
+ }
563
+ async deleteIndex({ indexName }) {
564
+ try {
565
+ await (await this.getClient()).collections.delete(this.toCollectionName(indexName));
566
+ } catch (error) {
567
+ throw new MastraError({
568
+ id: createVectorErrorId("WEAVIATE", "DELETE_INDEX", "FAILED"),
569
+ domain: ErrorDomain.STORAGE,
570
+ category: ErrorCategory.THIRD_PARTY,
571
+ details: { indexName }
572
+ }, error);
573
+ }
574
+ }
575
+ async updateVector({ indexName, id, filter, update }) {
576
+ try {
577
+ if (id !== void 0 && filter !== void 0) throw new MastraError({
578
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "INVALID_ARGS"),
579
+ text: "id and filter are mutually exclusive",
580
+ domain: ErrorDomain.STORAGE,
581
+ category: ErrorCategory.USER,
582
+ details: { indexName }
583
+ });
584
+ if (!update || !update.vector && !update.metadata) throw new MastraError({
585
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "INVALID_ARGS"),
586
+ text: "Update data is required: no updates provided",
587
+ domain: ErrorDomain.STORAGE,
588
+ category: ErrorCategory.USER,
589
+ details: { indexName }
590
+ });
591
+ if (id === void 0 && filter === void 0) throw new MastraError({
592
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "INVALID_ARGS"),
593
+ text: "Either id or filter must be provided",
594
+ domain: ErrorDomain.STORAGE,
595
+ category: ErrorCategory.USER,
596
+ details: { indexName }
597
+ });
598
+ if (id === void 0 && (typeof filter !== "object" || filter === null || Object.keys(filter).length === 0)) throw new MastraError({
599
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "INVALID_ARGS"),
600
+ text: "A non-empty filter is required: empty filter not allowed",
601
+ domain: ErrorDomain.STORAGE,
602
+ category: ErrorCategory.USER,
603
+ details: { indexName }
604
+ });
605
+ const collection = await this.getCollection(indexName);
606
+ if (update.metadata) await this.ensureProperties(collection, this.toCollectionName(indexName), [update.metadata]);
607
+ const encodedProperties = update.metadata ? encodeMetaProperties(update.metadata) : void 0;
608
+ const applyUpdate = async (uuid) => {
609
+ await collection.data.update({
610
+ id: uuid,
611
+ ...encodedProperties ? { properties: encodedProperties } : {},
612
+ ...update.vector ? { vectors: update.vector } : {}
613
+ });
614
+ };
615
+ if (id !== void 0) {
616
+ await applyUpdate(generateUuid5(id));
617
+ return;
618
+ }
619
+ const filters = this.transformFilter(collection, filter);
620
+ const PAGE_SIZE = 1e3;
621
+ const MAX_FILTERED_UPDATE = 1e4;
622
+ const uuids = [];
623
+ for (let offset = 0;; offset += PAGE_SIZE) {
624
+ if (offset >= MAX_FILTERED_UPDATE) throw new MastraError({
625
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "INVALID_ARGS"),
626
+ domain: ErrorDomain.STORAGE,
627
+ category: ErrorCategory.USER,
628
+ text: `Filtered updateVector matched more than ${MAX_FILTERED_UPDATE} vectors, which exceeds Weaviate's query result cap. Narrow the filter or update by id.`,
629
+ details: {
630
+ indexName,
631
+ limit: MAX_FILTERED_UPDATE
632
+ }
633
+ });
634
+ const page = await collection.query.fetchObjects({
635
+ limit: PAGE_SIZE,
636
+ offset,
637
+ ...filters ? { filters } : {}
638
+ });
639
+ for (const obj of page.objects) uuids.push(obj.uuid);
640
+ if (page.objects.length < PAGE_SIZE) break;
641
+ }
642
+ for (const uuid of uuids) await applyUpdate(uuid);
643
+ } catch (error) {
644
+ if (error instanceof MastraError) throw error;
645
+ throw new MastraError({
646
+ id: createVectorErrorId("WEAVIATE", "UPDATE_VECTOR", "FAILED"),
647
+ domain: ErrorDomain.STORAGE,
648
+ category: ErrorCategory.THIRD_PARTY,
649
+ details: {
650
+ indexName,
651
+ ...id ? { id } : {}
652
+ }
653
+ }, error);
654
+ }
655
+ }
656
+ async deleteVector({ indexName, id }) {
657
+ try {
658
+ await (await this.getCollection(indexName)).data.deleteById(generateUuid5(id));
659
+ } catch (error) {
660
+ throw new MastraError({
661
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTOR", "FAILED"),
662
+ domain: ErrorDomain.STORAGE,
663
+ category: ErrorCategory.THIRD_PARTY,
664
+ details: {
665
+ indexName,
666
+ id
667
+ }
668
+ }, error);
669
+ }
670
+ }
671
+ async deleteVectors({ indexName, filter, ids }) {
672
+ if (ids && filter) throw new MastraError({
673
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
674
+ text: "Cannot specify both ids and filter - they are mutually exclusive",
675
+ domain: ErrorDomain.STORAGE,
676
+ category: ErrorCategory.USER,
677
+ details: { indexName }
678
+ });
679
+ if (!ids && !filter) throw new MastraError({
680
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTORS", "NO_TARGET"),
681
+ text: "Either filter or ids must be provided",
682
+ domain: ErrorDomain.STORAGE,
683
+ category: ErrorCategory.USER,
684
+ details: { indexName }
685
+ });
686
+ if (ids && ids.length === 0) throw new MastraError({
687
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTORS", "EMPTY_IDS"),
688
+ text: "Cannot delete with empty ids array",
689
+ domain: ErrorDomain.STORAGE,
690
+ category: ErrorCategory.USER,
691
+ details: { indexName }
692
+ });
693
+ if (filter && typeof filter === "object" && Object.keys(filter).length === 0) throw new MastraError({
694
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTORS", "EMPTY_FILTER"),
695
+ text: "Cannot delete with empty filter object",
696
+ domain: ErrorDomain.STORAGE,
697
+ category: ErrorCategory.USER,
698
+ details: { indexName }
699
+ });
700
+ try {
701
+ const collection = await this.getCollection(indexName);
702
+ if (ids) for (const id of ids) await collection.data.deleteById(generateUuid5(id));
703
+ else if (filter) {
704
+ const filters = this.transformFilter(collection, filter);
705
+ if (filters) await collection.data.deleteMany(filters);
706
+ }
707
+ } catch (error) {
708
+ if (error instanceof MastraError) throw error;
709
+ throw new MastraError({
710
+ id: createVectorErrorId("WEAVIATE", "DELETE_VECTORS", "FAILED"),
711
+ domain: ErrorDomain.STORAGE,
712
+ category: ErrorCategory.THIRD_PARTY,
713
+ details: {
714
+ indexName,
715
+ ...filter && { filter: JSON.stringify(filter) },
716
+ ...ids && { idsCount: ids.length }
717
+ }
718
+ }, error);
719
+ }
720
+ }
721
+ };
722
+ //#endregion
723
+ //#region src/vector/prompt.ts
724
+ /**
725
+ * Vector store specific prompt that details supported operators and examples.
726
+ * This prompt helps users construct valid filters for Weaviate Vector.
727
+ */
728
+ const WEAVIATE_PROMPT = `When querying Weaviate, you can ONLY use the operators listed below. Any other operators will be rejected.
729
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
730
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
731
+
732
+ Basic Comparison Operators:
733
+ - $eq: Exact match (default when using field: value)
734
+ Example: { "category": "electronics" }
735
+ - $ne: Not equal
736
+ Example: { "category": { "$ne": "electronics" } }
737
+ - $gt: Greater than
738
+ Example: { "price": { "$gt": 100 } }
739
+ - $gte: Greater than or equal
740
+ Example: { "price": { "$gte": 100 } }
741
+ - $lt: Less than
742
+ Example: { "price": { "$lt": 100 } }
743
+ - $lte: Less than or equal
744
+ Example: { "price": { "$lte": 100 } }
745
+
746
+ Array Operators:
747
+ - $in: Match any value in array
748
+ Example: { "category": { "$in": ["electronics", "books"] } }
749
+ - $nin: Does not match any value in array
750
+ Example: { "category": { "$nin": ["electronics", "books"] } }
751
+ - $all: Match arrays containing all values
752
+ Example: { "tags": { "$all": ["sale", "new"] } }
753
+
754
+ Logical Operators:
755
+ - $and: Logical AND (can be implicit or explicit)
756
+ Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
757
+ Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
758
+ - $or: Logical OR
759
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
760
+ - $not: Logical NOT
761
+ Example: { "$not": { "category": "electronics" } }
762
+
763
+ Element Operators:
764
+ - $exists: Check if field exists (is not null)
765
+ Example: { "rating": { "$exists": true } }
766
+
767
+ Restrictions:
768
+ - Regex patterns are not supported
769
+ - Only $and, $or, and $not logical operators are supported at the top level
770
+ - Empty arrays in $in/$nin will return no results
771
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
772
+ - At least one key-value pair is required in filter object
773
+ - Empty objects and undefined values are treated as no filter
774
+ - Invalid types in comparison operators will throw errors
775
+ - All non-logical operators must be used within a field condition
776
+ Valid: { "field": { "$gt": 100 } }
777
+ Valid: { "$and": [...] }
778
+ Invalid: { "$gt": 100 }
779
+ - Logical operators must contain field conditions, not direct operators
780
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
781
+ Invalid: { "$and": [{ "$gt": 100 }] }
782
+ - Logical operators ($and, $or, $not):
783
+ - Can only be used at top level or nested within other logical operators
784
+ - Can not be used on a field level, or be nested inside a field
785
+ - Can not be used inside an operator
786
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
787
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
788
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
789
+
790
+ Example Complex Query:
791
+ {
792
+ "$and": [
793
+ { "category": { "$in": ["electronics", "computers"] } },
794
+ { "price": { "$gte": 100, "$lte": 1000 } },
795
+ { "rating": { "$exists": true, "$gt": 4 } },
796
+ { "$or": [
797
+ { "stock": { "$gt": 0 } },
798
+ { "preorder": { "$eq": true } }
799
+ ]},
800
+ { "$not": { "status": "discontinued" } }
801
+ ]
802
+ }`;
803
+ //#endregion
804
+ export { WEAVIATE_PROMPT, WeaviateVector };
805
+
806
+ //# sourceMappingURL=index.js.map