@yamo/memory-mesh 2.3.2 → 3.0.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 (102) hide show
  1. package/bin/memory_mesh.js +1 -1
  2. package/lib/llm/client.d.ts +111 -0
  3. package/lib/llm/client.js +299 -357
  4. package/lib/llm/client.ts +413 -0
  5. package/lib/llm/index.d.ts +17 -0
  6. package/lib/llm/index.js +15 -8
  7. package/lib/llm/index.ts +19 -0
  8. package/lib/memory/adapters/client.d.ts +183 -0
  9. package/lib/memory/adapters/client.js +518 -0
  10. package/lib/memory/adapters/client.ts +678 -0
  11. package/lib/memory/adapters/config.d.ts +137 -0
  12. package/lib/memory/adapters/config.js +189 -0
  13. package/lib/memory/adapters/config.ts +259 -0
  14. package/lib/memory/adapters/errors.d.ts +76 -0
  15. package/lib/memory/adapters/errors.js +128 -0
  16. package/lib/memory/adapters/errors.ts +166 -0
  17. package/lib/memory/context-manager.d.ts +44 -0
  18. package/lib/memory/context-manager.js +344 -0
  19. package/lib/memory/context-manager.ts +432 -0
  20. package/lib/memory/embeddings/factory.d.ts +59 -0
  21. package/lib/memory/embeddings/factory.js +148 -0
  22. package/lib/{embeddings/factory.js → memory/embeddings/factory.ts} +69 -28
  23. package/lib/memory/embeddings/index.d.ts +2 -0
  24. package/lib/memory/embeddings/index.js +2 -0
  25. package/lib/memory/embeddings/index.ts +2 -0
  26. package/lib/memory/embeddings/service.d.ts +164 -0
  27. package/lib/memory/embeddings/service.js +515 -0
  28. package/lib/{embeddings/service.js → memory/embeddings/service.ts} +223 -156
  29. package/lib/memory/index.d.ts +9 -0
  30. package/lib/memory/index.js +9 -1
  31. package/lib/memory/index.ts +20 -0
  32. package/lib/memory/memory-mesh.d.ts +274 -0
  33. package/lib/memory/memory-mesh.js +1469 -678
  34. package/lib/memory/memory-mesh.ts +1803 -0
  35. package/lib/memory/memory-translator.d.ts +19 -0
  36. package/lib/memory/memory-translator.js +125 -0
  37. package/lib/memory/memory-translator.ts +158 -0
  38. package/lib/memory/schema.d.ts +111 -0
  39. package/lib/memory/schema.js +183 -0
  40. package/lib/memory/schema.ts +267 -0
  41. package/lib/memory/scorer.d.ts +26 -0
  42. package/lib/memory/scorer.js +77 -0
  43. package/lib/memory/scorer.ts +95 -0
  44. package/lib/memory/search/index.d.ts +1 -0
  45. package/lib/memory/search/index.js +1 -0
  46. package/lib/memory/search/index.ts +1 -0
  47. package/lib/memory/search/keyword-search.d.ts +62 -0
  48. package/lib/memory/search/keyword-search.js +135 -0
  49. package/lib/{search/keyword-search.js → memory/search/keyword-search.ts} +66 -36
  50. package/lib/scrubber/config/defaults.d.ts +53 -0
  51. package/lib/scrubber/config/defaults.js +49 -57
  52. package/lib/scrubber/config/defaults.ts +117 -0
  53. package/lib/scrubber/index.d.ts +6 -0
  54. package/lib/scrubber/index.js +3 -23
  55. package/lib/scrubber/index.ts +7 -0
  56. package/lib/scrubber/scrubber.d.ts +61 -0
  57. package/lib/scrubber/scrubber.js +99 -121
  58. package/lib/scrubber/scrubber.ts +168 -0
  59. package/lib/scrubber/stages/chunker.d.ts +13 -0
  60. package/lib/scrubber/stages/metadata-annotator.d.ts +18 -0
  61. package/lib/scrubber/stages/normalizer.d.ts +13 -0
  62. package/lib/scrubber/stages/semantic-filter.d.ts +13 -0
  63. package/lib/scrubber/stages/structural-cleaner.d.ts +13 -0
  64. package/lib/scrubber/stages/validator.d.ts +18 -0
  65. package/lib/scrubber/telemetry.d.ts +36 -0
  66. package/lib/scrubber/telemetry.js +53 -58
  67. package/lib/scrubber/telemetry.ts +99 -0
  68. package/lib/utils/logger.d.ts +29 -0
  69. package/lib/utils/logger.js +64 -0
  70. package/lib/utils/logger.ts +85 -0
  71. package/lib/utils/skill-metadata.d.ts +32 -0
  72. package/lib/utils/skill-metadata.js +132 -0
  73. package/lib/utils/skill-metadata.ts +147 -0
  74. package/lib/yamo/emitter.d.ts +73 -0
  75. package/lib/yamo/emitter.js +78 -143
  76. package/lib/yamo/emitter.ts +249 -0
  77. package/lib/yamo/schema.d.ts +58 -0
  78. package/lib/yamo/schema.js +81 -108
  79. package/lib/yamo/schema.ts +165 -0
  80. package/package.json +11 -8
  81. package/index.d.ts +0 -111
  82. package/lib/embeddings/index.js +0 -2
  83. package/lib/index.js +0 -6
  84. package/lib/lancedb/client.js +0 -633
  85. package/lib/lancedb/config.js +0 -215
  86. package/lib/lancedb/errors.js +0 -144
  87. package/lib/lancedb/index.js +0 -4
  88. package/lib/lancedb/schema.js +0 -217
  89. package/lib/scrubber/errors/scrubber-error.js +0 -43
  90. package/lib/scrubber/stages/chunker.js +0 -103
  91. package/lib/scrubber/stages/metadata-annotator.js +0 -74
  92. package/lib/scrubber/stages/normalizer.js +0 -59
  93. package/lib/scrubber/stages/semantic-filter.js +0 -61
  94. package/lib/scrubber/stages/structural-cleaner.js +0 -82
  95. package/lib/scrubber/stages/validator.js +0 -66
  96. package/lib/scrubber/utils/hash.js +0 -39
  97. package/lib/scrubber/utils/html-parser.js +0 -45
  98. package/lib/scrubber/utils/pattern-matcher.js +0 -63
  99. package/lib/scrubber/utils/token-counter.js +0 -31
  100. package/lib/search/index.js +0 -1
  101. package/lib/utils/index.js +0 -1
  102. package/lib/yamo/index.js +0 -15
@@ -0,0 +1,518 @@
1
+ /**
2
+ * LanceDB Client Wrapper
3
+ *
4
+ * A comprehensive wrapper around LanceDB JavaScript SDK providing:
5
+ * - Connection management with pooling and retries
6
+ * - CRUD operations for memory entries
7
+ * - Vector similarity search with filtering
8
+ * - Database statistics and monitoring
9
+ *
10
+ * @class LanceDBClient
11
+ */
12
+ import * as lancedb from "@lancedb/lancedb";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import { createMemoryTableWithDimension, DEFAULT_VECTOR_DIMENSION, } from "../schema.js";
16
+ import { StorageError, QueryError } from "./errors.js";
17
+ import { createLogger } from "../../utils/logger.js";
18
+ const logger = createLogger("lancedb-client");
19
+ /**
20
+ * LanceDB Client wrapper class
21
+ */
22
+ export class LanceDBClient {
23
+ uri;
24
+ tableName;
25
+ maxRetries;
26
+ retryDelay;
27
+ vectorDimension;
28
+ driver;
29
+ db;
30
+ table;
31
+ isConnected;
32
+ tempDir; // Track temp dirs for cleanup
33
+ /**
34
+ * Create a new LanceDBClient instance
35
+ * @param {Object} [config={}] - Configuration object
36
+ */
37
+ constructor(config = {}) {
38
+ this.uri =
39
+ (config && config.uri) || process.env.LANCEDB_URI || "./data/lancedb";
40
+ this.tableName =
41
+ (config && config.tableName) ||
42
+ process.env.LANCEDB_MEMORY_TABLE ||
43
+ "memory_entries";
44
+ this.maxRetries = (config && config.maxRetries) || 3;
45
+ this.retryDelay = (config && config.retryDelay) || 1000;
46
+ this.vectorDimension =
47
+ (config && config.vectorDimension) || DEFAULT_VECTOR_DIMENSION;
48
+ this.driver = (config && config.driver) || lancedb;
49
+ // Connection state
50
+ this.db = null;
51
+ this.table = null;
52
+ this.isConnected = false;
53
+ }
54
+ /**
55
+ * Connect to LanceDB and initialize table
56
+ * Creates the database directory and table if they don't exist
57
+ * @returns {Promise<void>}
58
+ * @throws {StorageError} If connection fails after retries
59
+ */
60
+ async connect() {
61
+ if (this.isConnected) {
62
+ return; // Already connected
63
+ }
64
+ let lastError = null;
65
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
66
+ try {
67
+ // Handle :memory: specially - LanceDB doesn't support true in-memory DBs
68
+ // Use OS temp directory for isolation
69
+ let dbPath = this.uri;
70
+ if (this.uri === ":memory:") {
71
+ const os = await import("os");
72
+ const crypto = await import("crypto");
73
+ const randomId = crypto.randomBytes(8).toString("hex");
74
+ dbPath = path.join(os.tmpdir(), `yamo-memory-${randomId}`);
75
+ this.tempDir = dbPath; // Track for cleanup
76
+ }
77
+ // Ensure database directory exists
78
+ const resolvedPath = path.resolve(dbPath);
79
+ const dbDir = path.dirname(resolvedPath);
80
+ if (!fs.existsSync(dbDir)) {
81
+ fs.mkdirSync(dbDir, { recursive: true });
82
+ }
83
+ // Connect to database
84
+ this.db = await this.driver.connect(dbPath);
85
+ // Initialize table with dynamic dimension (creates if doesn't exist, opens if it does)
86
+ if (this.db) {
87
+ this.table = await createMemoryTableWithDimension(this.db, this.tableName, this.vectorDimension);
88
+ }
89
+ this.isConnected = true;
90
+ return;
91
+ }
92
+ catch (error) {
93
+ lastError = error;
94
+ const msg = error.message.toLowerCase();
95
+ // Specific check for locking/busy errors
96
+ if (msg.includes("busy") ||
97
+ msg.includes("locked") ||
98
+ msg.includes("resource temporarily unavailable")) {
99
+ logger.warn({ attempt, maxRetries: this.maxRetries, uri: this.uri }, "Database is locked by another process, retrying");
100
+ await this._sleep(this.retryDelay * attempt + Math.random() * 1000);
101
+ continue;
102
+ }
103
+ if (attempt < this.maxRetries) {
104
+ // Wait before retrying for other errors
105
+ await this._sleep(this.retryDelay * attempt);
106
+ }
107
+ }
108
+ }
109
+ // All retries failed
110
+ const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
111
+ throw new StorageError(`Failed to connect to LanceDB after ${this.maxRetries} attempts: ${errorMessage}`, { uri: this.uri, tableName: this.tableName, originalError: lastError });
112
+ }
113
+ /**
114
+ * Disconnect from LanceDB
115
+ * @returns {Promise<void>}
116
+ */
117
+ disconnect() {
118
+ this.db = null;
119
+ this.table = null;
120
+ this.isConnected = false;
121
+ // Clean up temp directory if we created one for :memory:
122
+ if (this.tempDir && fs.existsSync(this.tempDir)) {
123
+ try {
124
+ fs.rmSync(this.tempDir, { recursive: true, force: true });
125
+ }
126
+ catch (_e) {
127
+ // Best-effort cleanup, ignore errors
128
+ }
129
+ this.tempDir = undefined;
130
+ }
131
+ }
132
+ /**
133
+ * Add a single memory entry
134
+ * @param {Object} data - Entry data
135
+ * @returns {Promise<Object>} Result with id and success status
136
+ * @throws {StorageError} If add operation fails
137
+ */
138
+ async add(data) {
139
+ if (!this.isConnected) {
140
+ await this.connect();
141
+ }
142
+ this._validateRecord(data);
143
+ return this._retryOperation(async () => {
144
+ const record = {
145
+ ...data,
146
+ created_at: new Date(),
147
+ updated_at: new Date(),
148
+ };
149
+ if (!this.table) {
150
+ throw new StorageError("Table not initialized");
151
+ }
152
+ await this.table.add([record]);
153
+ return {
154
+ id: data.id,
155
+ success: true,
156
+ };
157
+ });
158
+ }
159
+ /**
160
+ * Add multiple memory entries in batch
161
+ * @param {Array<Object>} records - Array of entry data objects
162
+ * @returns {Promise<Object>} Result with count of added records
163
+ * @throws {StorageError} If batch add fails
164
+ */
165
+ async addBatch(records) {
166
+ if (!this.isConnected) {
167
+ await this.connect();
168
+ }
169
+ if (!Array.isArray(records) || records.length === 0) {
170
+ throw new StorageError("Records must be a non-empty array");
171
+ }
172
+ // Validate all records
173
+ records.forEach((record) => this._validateRecord(record));
174
+ return this._retryOperation(async () => {
175
+ const now = new Date();
176
+ const recordsWithTimestamps = records.map((record) => ({
177
+ ...record,
178
+ created_at: now,
179
+ updated_at: now,
180
+ }));
181
+ if (!this.table) {
182
+ throw new StorageError("Table not initialized");
183
+ }
184
+ await this.table.add(recordsWithTimestamps);
185
+ return {
186
+ count: records.length,
187
+ success: true,
188
+ };
189
+ });
190
+ }
191
+ /**
192
+ * Search for similar vectors
193
+ * @param {Array<number>} vector - Query vector (384 dimensions)
194
+ * @param {Object} options - Search options
195
+ * @returns {Promise<Array<Object>>} Array of search results with scores
196
+ * @throws {QueryError} If search fails
197
+ */
198
+ async search(vector, options = {}) {
199
+ if (!this.isConnected) {
200
+ await this.connect();
201
+ }
202
+ this._validateVector(vector);
203
+ const { limit = 10, nprobes = 20, filter = null } = options;
204
+ return this._retryOperation(async () => {
205
+ if (!this.table) {
206
+ throw new StorageError("Table not initialized");
207
+ }
208
+ // Build the search query with all applicable options
209
+ let query = this.table.search(vector);
210
+ // Apply nprobes for IVF index (if supported)
211
+ if (nprobes && typeof nprobes === "number") {
212
+ try {
213
+ query = query.nprobes(nprobes);
214
+ }
215
+ catch (_e) {
216
+ // ignore
217
+ }
218
+ }
219
+ // Apply filter if provided
220
+ if (filter) {
221
+ query = query.where(filter);
222
+ }
223
+ // Execute search with limit
224
+ const resultsArray = await query.limit(limit).toArray();
225
+ return resultsArray.map((row) => ({
226
+ id: row.id,
227
+ content: row.content,
228
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
229
+ // _distance is internal LanceDB property
230
+ score: row._distance,
231
+ created_at: row.created_at,
232
+ vector: row.vector, // Include vector if returned
233
+ }));
234
+ });
235
+ }
236
+ /**
237
+ * Get a record by ID
238
+ * @param {string} id - Record ID
239
+ * @returns {Promise<Object|null>} Record object or null if not found
240
+ * @throws {QueryError} If query fails
241
+ */
242
+ async getById(id) {
243
+ if (!this.isConnected) {
244
+ await this.connect();
245
+ }
246
+ return this._retryOperation(async () => {
247
+ if (!this.table) {
248
+ throw new StorageError("Table not initialized");
249
+ }
250
+ // Use a simple filter query instead of search
251
+ const resultsArray = await this.table
252
+ .query()
253
+ .where(`id == '${this._sanitizeId(id)}'`)
254
+ .toArray();
255
+ if (resultsArray.length === 0) {
256
+ return null;
257
+ }
258
+ const record = resultsArray[0];
259
+ return {
260
+ id: record.id,
261
+ vector: record.vector,
262
+ content: record.content,
263
+ metadata: record.metadata
264
+ ? JSON.parse(record.metadata)
265
+ : null,
266
+ created_at: record.created_at,
267
+ updated_at: record.updated_at,
268
+ };
269
+ });
270
+ }
271
+ /**
272
+ * Get all records from the database
273
+ * @param {Object} options - Options
274
+ * @returns {Promise<Array<Object>>} Array of all records
275
+ */
276
+ async getAll(options = {}) {
277
+ if (!this.isConnected) {
278
+ await this.connect();
279
+ }
280
+ return this._retryOperation(async () => {
281
+ if (!this.table) {
282
+ throw new StorageError("Table not initialized");
283
+ }
284
+ let query = this.table.query();
285
+ if (options.limit) {
286
+ query = query.limit(options.limit);
287
+ }
288
+ const resultsArray = await query.toArray();
289
+ return resultsArray.map((row) => ({
290
+ id: row.id,
291
+ content: row.content,
292
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
293
+ vector: row.vector,
294
+ created_at: row.created_at,
295
+ updated_at: row.updated_at,
296
+ }));
297
+ });
298
+ }
299
+ /**
300
+ * Delete a record by ID
301
+ * @param {string} id - Record ID to delete
302
+ * @returns {Promise<Object>} Result with success status
303
+ * @throws {StorageError} If delete fails
304
+ */
305
+ async delete(id) {
306
+ if (!this.isConnected) {
307
+ await this.connect();
308
+ }
309
+ return this._retryOperation(async () => {
310
+ if (!this.table) {
311
+ throw new StorageError("Table not initialized");
312
+ }
313
+ await this.table.delete(`id == '${this._sanitizeId(id)}'`);
314
+ return {
315
+ id,
316
+ success: true,
317
+ };
318
+ });
319
+ }
320
+ /**
321
+ * Update an existing record
322
+ * @param {string} id - Record ID to update
323
+ * @param {Object} data - Updated data fields
324
+ * @returns {Promise<Object>} Result with success status
325
+ * @throws {StorageError} If update fails
326
+ */
327
+ async update(id, data) {
328
+ if (!this.isConnected) {
329
+ await this.connect();
330
+ }
331
+ return this._retryOperation(async () => {
332
+ const updateData = {
333
+ ...data,
334
+ updated_at: new Date(),
335
+ };
336
+ if (!this.table) {
337
+ throw new StorageError("Table not initialized");
338
+ }
339
+ // Update API expects filter and values separately
340
+ await this.table.update({
341
+ where: `id == '${this._sanitizeId(id)}'`,
342
+ values: updateData,
343
+ });
344
+ return {
345
+ id,
346
+ success: true,
347
+ };
348
+ });
349
+ }
350
+ /**
351
+ * Get database statistics
352
+ * @returns {Promise<Object>} Statistics including count, size, etc.
353
+ * @throws {QueryError} If stats query fails
354
+ */
355
+ async getStats() {
356
+ if (!this.isConnected) {
357
+ await this.connect();
358
+ }
359
+ return this._retryOperation(async () => {
360
+ if (!this.table) {
361
+ throw new StorageError("Table not initialized");
362
+ }
363
+ let count = 0;
364
+ try {
365
+ if (typeof this.table.count === "function") {
366
+ count = await this.table.count();
367
+ }
368
+ else {
369
+ // Fallback: use a limited query to avoid loading all records
370
+ const countResults = await this.table.query().execute();
371
+ for await (const batch of countResults) {
372
+ count += batch.numRows;
373
+ }
374
+ }
375
+ }
376
+ catch (_countError) {
377
+ count = -1;
378
+ }
379
+ return {
380
+ tableName: this.tableName,
381
+ uri: this.uri,
382
+ count: count,
383
+ isConnected: this.isConnected,
384
+ };
385
+ });
386
+ }
387
+ /**
388
+ * Sanitize an ID to prevent SQL injection
389
+ * Removes any characters that aren't alphanumeric, underscore, or hyphen
390
+ * @private
391
+ */
392
+ _sanitizeId(id) {
393
+ // Remove any characters that aren't alphanumeric, underscore, or hyphen
394
+ // This prevents SQL injection via raw string interpolation in queries
395
+ return id.replace(/[^a-zA-Z0-9_-]/g, "");
396
+ }
397
+ /**
398
+ * Validate a record object
399
+ * @private
400
+ */
401
+ _validateRecord(record) {
402
+ if (!record || typeof record !== "object") {
403
+ throw new StorageError("Record must be an object");
404
+ }
405
+ if (!record.id) {
406
+ throw new StorageError("Record must have an id field");
407
+ }
408
+ if (!record.content) {
409
+ throw new StorageError("Record must have a content field");
410
+ }
411
+ if (!record.vector) {
412
+ throw new StorageError("Record must have a vector field");
413
+ }
414
+ this._validateVector(record.vector);
415
+ }
416
+ /**
417
+ * Validate a vector array
418
+ * @private
419
+ */
420
+ _validateVector(vector) {
421
+ if (!Array.isArray(vector)) {
422
+ throw new QueryError("Vector must be an array");
423
+ }
424
+ // Expected dimension for all-MiniLM-L6-v2 model
425
+ // This should ideally match this.vectorDimension
426
+ // But keeping as is to match original logic or update to use this.vectorDimension
427
+ const expectedDim = this.vectorDimension || 384;
428
+ if (vector.length !== expectedDim) {
429
+ // Loose validation for now as different models have different dims
430
+ // throw new QueryError(`Vector must have ${expectedDim} dimensions, got ${vector.length}`);
431
+ }
432
+ // Validate all elements are numbers
433
+ for (let i = 0; i < vector.length; i++) {
434
+ if (typeof vector[i] !== "number" || isNaN(vector[i])) {
435
+ throw new QueryError(`Vector element ${i} is not a valid number`);
436
+ }
437
+ }
438
+ }
439
+ /**
440
+ * Sleep for a specified duration
441
+ * @private
442
+ */
443
+ _sleep(ms) {
444
+ return new Promise((resolve) => setTimeout(resolve, ms));
445
+ }
446
+ /**
447
+ * Check if an error is retryable (transient network/connection issues)
448
+ * @private
449
+ */
450
+ _isRetryableError(error) {
451
+ if (!error || !error.message) {
452
+ return false;
453
+ }
454
+ const message = error.message.toLowerCase();
455
+ // Network-related errors
456
+ const retryablePatterns = [
457
+ "econnreset", // Connection reset by peer
458
+ "etimedout", // Operation timed out
459
+ "enotfound", // DNS resolution failed
460
+ "econnrefused", // Connection refused
461
+ "enetunreach", // Network unreachable
462
+ "ehostunreach", // Host unreachable
463
+ "socket hang up", // Socket closed unexpectedly
464
+ "network error", // Generic network error
465
+ "failed to fetch", // Fetch/network failure
466
+ "timeout", // Timeout occurred
467
+ ];
468
+ // Check for network patterns
469
+ const hasNetworkPattern = retryablePatterns.some((pattern) => message.includes(pattern));
470
+ // Check for 5xx HTTP errors (server-side errors that may be transient)
471
+ const hasServerError = /5\d{2}/.test(message);
472
+ // Check for specific LanceDB/lancedb errors that may be transient
473
+ const lancedbRetryable = [
474
+ "connection",
475
+ "database closed",
476
+ "table not found",
477
+ "lock",
478
+ "busy",
479
+ "temporary",
480
+ ].some((pattern) => message.includes(pattern));
481
+ return hasNetworkPattern || hasServerError || lancedbRetryable;
482
+ }
483
+ /**
484
+ * Retry an operation with exponential backoff
485
+ * @private
486
+ */
487
+ async _retryOperation(operation, maxRetries, baseDelay) {
488
+ const max = maxRetries ?? this.maxRetries;
489
+ const delay = baseDelay ?? this.retryDelay;
490
+ let lastError = null;
491
+ for (let attempt = 1; attempt <= max; attempt++) {
492
+ try {
493
+ return await operation();
494
+ }
495
+ catch (error) {
496
+ lastError = error;
497
+ if (!this._isRetryableError(error)) {
498
+ throw error;
499
+ }
500
+ if (attempt === max) {
501
+ throw error;
502
+ }
503
+ const backoffMs = delay * Math.pow(2, attempt - 1);
504
+ const jitterMs = backoffMs * Math.random() * 0.25;
505
+ const message = error instanceof Error ? error.message : String(error);
506
+ logger.debug({
507
+ attempt,
508
+ max,
509
+ message,
510
+ retryDelayMs: Math.round(backoffMs + jitterMs),
511
+ }, "Retryable error, retrying");
512
+ await this._sleep(backoffMs + jitterMs);
513
+ }
514
+ }
515
+ throw lastError;
516
+ }
517
+ }
518
+ export default LanceDBClient;