@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
@@ -1,633 +0,0 @@
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
-
13
- import lancedb from "@lancedb/lancedb";
14
- import fs from "fs";
15
- import path from "path";
16
- import { createMemoryTableWithDimension, DEFAULT_VECTOR_DIMENSION } from "./schema.js";
17
- import { StorageError, QueryError, ConfigurationError } from "./errors.js";
18
-
19
- /**
20
- * LanceDB Client wrapper class
21
- */
22
- class LanceDBClient {
23
- /**
24
- * Create a new LanceDBClient instance
25
- * @param {Object} [config={}] - Configuration object
26
- * @param {string} [config.uri] - Database URI (default: from env or './data/lancedb')
27
- * @param {string} [config.tableName] - Table name (default: from env or 'memory_entries')
28
- * @param {number} [config.maxRetries] - Maximum connection retries (default: 3)
29
- * @param {number} [config.retryDelay] - Delay between retries in ms (default: 1000)
30
- * @param {number} [config.vectorDimension] - Vector dimension for embeddings (default: 384)
31
- * @param {Object} [config.driver] - LanceDB driver instance (for testing)
32
- */
33
- constructor(config = {}) {
34
- this.uri = (config && config.uri) || process.env.LANCEDB_URI || './data/lancedb';
35
- this.tableName = (config && config.tableName) || process.env.LANCEDB_MEMORY_TABLE || 'memory_entries';
36
- this.maxRetries = (config && config.maxRetries) || 3;
37
- this.retryDelay = (config && config.retryDelay) || 1000;
38
- this.vectorDimension = (config && config.vectorDimension) || DEFAULT_VECTOR_DIMENSION;
39
- this.driver = (config && config.driver) || lancedb;
40
-
41
- // Connection state
42
- this.db = null;
43
- this.table = null;
44
- this.isConnected = false;
45
- }
46
-
47
- /**
48
- * Connect to LanceDB and initialize table
49
- * Creates the database directory and table if they don't exist
50
- * @returns {Promise<void>}
51
- * @throws {StorageError} If connection fails after retries
52
- */
53
- async connect() {
54
- if (this.isConnected) {
55
- return; // Already connected
56
- }
57
-
58
- let lastError = null;
59
-
60
- for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
61
- try {
62
- // Ensure database directory exists
63
- const dbPath = path.resolve(this.uri);
64
- const dbDir = path.dirname(dbPath);
65
-
66
- if (!fs.existsSync(dbDir)) {
67
- fs.mkdirSync(dbDir, { recursive: true });
68
- }
69
-
70
- // Connect to database
71
- this.db = await this.driver.connect(this.uri);
72
-
73
- // Initialize table with dynamic dimension (creates if doesn't exist, opens if it does)
74
- this.table = await createMemoryTableWithDimension(this.db, this.tableName, this.vectorDimension);
75
-
76
- this.isConnected = true;
77
- return;
78
-
79
- } catch (error) {
80
- lastError = error;
81
-
82
- if (attempt < this.maxRetries) {
83
- // Wait before retrying
84
- await this._sleep(this.retryDelay * attempt);
85
- }
86
- }
87
- }
88
-
89
- // All retries failed
90
- const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
91
- throw new StorageError(
92
- `Failed to connect to LanceDB after ${this.maxRetries} attempts: ${errorMessage}`,
93
- { uri: this.uri, tableName: this.tableName, originalError: lastError }
94
- );
95
- }
96
-
97
- /**
98
- * Disconnect from LanceDB
99
- * @returns {Promise<void>}
100
- */
101
- async disconnect() {
102
- this.db = null;
103
- this.table = null;
104
- this.isConnected = false;
105
- }
106
-
107
- /**
108
- * Add a single memory entry
109
- * @param {Object} data - Entry data
110
- * @param {string} data.id - Unique identifier
111
- * @param {Array<number>} data.vector - Embedding vector (384 dimensions)
112
- * @param {string} data.content - Text content
113
- * @param {string} [data.metadata] - JSON string metadata
114
- * @returns {Promise<Object>} Result with id and success status
115
- * @throws {StorageError} If add operation fails
116
- */
117
- async add(data) {
118
- if (!this.isConnected) {
119
- await this.connect();
120
- }
121
-
122
- this._validateRecord(data);
123
-
124
- return await this._retryOperation(async () => {
125
- const record = {
126
- ...data,
127
- created_at: new Date(),
128
- updated_at: new Date()
129
- };
130
-
131
- if (!this.table) {
132
- throw new StorageError('Table not initialized');
133
- }
134
-
135
- await this.table.add([record]);
136
-
137
- return {
138
- id: data.id,
139
- success: true
140
- };
141
- });
142
- }
143
-
144
- /**
145
- * Add multiple memory entries in batch
146
- * @param {Array<Object>} records - Array of entry data objects
147
- * @returns {Promise<Object>} Result with count of added records
148
- * @throws {StorageError} If batch add fails
149
- */
150
- async addBatch(records) {
151
- if (!this.isConnected) {
152
- await this.connect();
153
- }
154
-
155
- if (!Array.isArray(records) || records.length === 0) {
156
- throw new StorageError('Records must be a non-empty array');
157
- }
158
-
159
- // Validate all records
160
- records.forEach(record => this._validateRecord(record));
161
-
162
- return await this._retryOperation(async () => {
163
- const now = new Date();
164
- const recordsWithTimestamps = records.map(record => ({
165
- ...record,
166
- created_at: now,
167
- updated_at: now
168
- }));
169
-
170
- if (!this.table) {
171
- throw new StorageError('Table not initialized');
172
- }
173
-
174
- await this.table.add(recordsWithTimestamps);
175
-
176
- return {
177
- count: records.length,
178
- success: true
179
- };
180
- });
181
- }
182
-
183
- /**
184
- * Search for similar vectors
185
- * @param {Array<number>} vector - Query vector (384 dimensions)
186
- * @param {Object} options - Search options
187
- * @param {number} [options.limit=10] - Maximum number of results
188
- * @param {string} [options.metric='cosine'] - Distance metric ('cosine', 'l2', 'dot')
189
- * @param {number} [options.nprobes=20] - Number of IVF partitions to search
190
- * @param {Object} [options.filter] - Filter expression for metadata (e.g., "content == 'value'")
191
- * Note: Filters work on top-level schema fields only.
192
- * The metadata field is stored as JSON string and cannot
193
- * be filtered directly. Use content or other top-level fields.
194
- * @returns {Promise<Array<Object>>} Array of search results with scores
195
- * @throws {QueryError} If search fails
196
- */
197
- async search(vector, options = {}) {
198
- if (!this.isConnected) {
199
- await this.connect();
200
- }
201
-
202
- this._validateVector(vector);
203
-
204
- const {
205
- limit = 10,
206
- metric = 'cosine',
207
- nprobes = 20,
208
- filter = null
209
- } = options;
210
-
211
- return await this._retryOperation(async () => {
212
- if (!this.table) {
213
- throw new StorageError('Table not initialized');
214
- }
215
-
216
- // Build the search query with all applicable options
217
- let query = this.table.search(vector);
218
-
219
- // Apply nprobes for IVF index (if supported)
220
- // Note: nprobes is typically set at index creation time, but we attempt to apply it here
221
- if (nprobes && typeof nprobes === 'number') {
222
- try {
223
- // @ts-ignore - nprobes might not exist on all query types or versions
224
- query = query.nprobes(nprobes);
225
- } catch (e) {
226
- // nprobes may not be supported in all LanceDB versions or configurations
227
- // Silently continue if not applicable
228
- }
229
- }
230
-
231
- // Apply filter if provided
232
- // LanceDB supports filtering with .where() clause
233
- if (filter) {
234
- query = query.where(filter);
235
- }
236
-
237
- // Execute search with limit
238
- // @ts-ignore - execute() is protected in types but public in JS implementation or types are wrong
239
- const resultsGenerator = await query.limit(limit).execute();
240
- const resultsArray = [];
241
-
242
- for await (const batch of resultsGenerator) {
243
- // Convert RecordBatch to array of StructRow objects
244
- const rows = batch.toArray();
245
- for (const row of rows) {
246
- resultsArray.push({
247
- id: row.id,
248
- content: row.content,
249
- metadata: row.metadata ? JSON.parse(row.metadata) : null,
250
- // @ts-ignore - _distance is internal property
251
- score: row._distance,
252
- created_at: row.created_at
253
- });
254
- }
255
- }
256
-
257
- return resultsArray;
258
- });
259
- }
260
-
261
- /**
262
- * Get a record by ID
263
- * @param {string} id - Record ID
264
- * @returns {Promise<Object|null>} Record object or null if not found
265
- * @throws {QueryError} If query fails
266
- */
267
- async getById(id) {
268
- if (!this.isConnected) {
269
- await this.connect();
270
- }
271
-
272
- return await this._retryOperation(async () => {
273
- if (!this.table) {
274
- throw new StorageError('Table not initialized');
275
- }
276
-
277
- // Use a simple filter query instead of search
278
- const results = await this.table.query()
279
- .where(`id == '${id}'`)
280
- // @ts-ignore
281
- .execute();
282
-
283
- // Convert AsyncGenerator of RecordBatches to array
284
- const resultsArray = [];
285
- for await (const batch of results) {
286
- const rows = batch.toArray();
287
- resultsArray.push(...rows);
288
- }
289
-
290
- if (resultsArray.length === 0) {
291
- return null;
292
- }
293
-
294
- const record = resultsArray[0];
295
- return {
296
- id: record.id,
297
- vector: record.vector,
298
- content: record.content,
299
- metadata: record.metadata ? JSON.parse(record.metadata) : null,
300
- created_at: record.created_at,
301
- updated_at: record.updated_at
302
- };
303
- });
304
- }
305
-
306
- /**
307
- * Get all records from the database
308
- * @param {Object} options - Options
309
- * @param {number} [options.limit] - Optional limit
310
- * @returns {Promise<Array<Object>>} Array of all records
311
- */
312
- async getAll(options = {}) {
313
- if (!this.isConnected) {
314
- await this.connect();
315
- }
316
-
317
- return await this._retryOperation(async () => {
318
- if (!this.table) {
319
- throw new StorageError('Table not initialized');
320
- }
321
-
322
- let query = this.table.query();
323
-
324
- if (options.limit) {
325
- query = query.limit(options.limit);
326
- }
327
-
328
- // @ts-ignore
329
- const results = await query.execute();
330
- const resultsArray = [];
331
-
332
- for await (const batch of results) {
333
- const rows = batch.toArray();
334
- for (const row of rows) {
335
- resultsArray.push({
336
- id: row.id,
337
- content: row.content,
338
- metadata: row.metadata ? JSON.parse(row.metadata) : null,
339
- vector: row.vector,
340
- created_at: row.created_at,
341
- updated_at: row.updated_at
342
- });
343
- }
344
- }
345
-
346
- return resultsArray;
347
- });
348
- }
349
-
350
- /**
351
- * Delete a record by ID
352
- * @param {string} id - Record ID to delete
353
- * @returns {Promise<Object>} Result with success status
354
- * @throws {StorageError} If delete fails
355
- */
356
- async delete(id) {
357
- if (!this.isConnected) {
358
- await this.connect();
359
- }
360
-
361
- return await this._retryOperation(async () => {
362
- if (!this.table) {
363
- throw new StorageError('Table not initialized');
364
- }
365
-
366
- await this.table.delete(`id == '${id}'`);
367
-
368
- return {
369
- id,
370
- success: true
371
- };
372
- });
373
- }
374
-
375
- /**
376
- * Update an existing record
377
- * @param {string} id - Record ID to update
378
- * @param {Object} data - Updated data fields
379
- * @returns {Promise<Object>} Result with success status
380
- * @throws {StorageError} If update fails
381
- */
382
- async update(id, data) {
383
- if (!this.isConnected) {
384
- await this.connect();
385
- }
386
-
387
- return await this._retryOperation(async () => {
388
- const updateData = {
389
- ...data,
390
- updated_at: new Date()
391
- };
392
-
393
- if (!this.table) {
394
- throw new StorageError('Table not initialized');
395
- }
396
-
397
- // Update API expects filter and values separately
398
- await this.table.update({
399
- where: `id == '${id}'`,
400
- values: updateData
401
- });
402
-
403
- return {
404
- id,
405
- success: true
406
- };
407
- });
408
- }
409
-
410
- /**
411
- * Get database statistics
412
- * @returns {Promise<Object>} Statistics including count, size, etc.
413
- * @throws {QueryError} If stats query fails
414
- */
415
- async getStats() {
416
- if (!this.isConnected) {
417
- await this.connect();
418
- }
419
-
420
- return await this._retryOperation(async () => {
421
- if (!this.table) {
422
- throw new StorageError('Table not initialized');
423
- }
424
-
425
- // Try to get count using table.count() method if available
426
- let count = 0;
427
- try {
428
- // LanceDB tables may have a count() method
429
- // @ts-ignore
430
- if (typeof this.table.count === 'function') {
431
- // @ts-ignore
432
- count = await this.table.count();
433
- } else {
434
- // Fallback: use a limited query to avoid loading all records
435
- // @ts-ignore
436
- const results = await this.table.query().limit(0).execute();
437
- // Try to extract count from metadata if available
438
- for await (const batch of results) {
439
- // Some LanceDB versions provide count in metadata
440
- if (batch.numRows !== undefined) {
441
- count = batch.numRows;
442
- break;
443
- }
444
- }
445
- // If count is still 0, we need to actually count
446
- if (count === 0) {
447
- // @ts-ignore
448
- const countResults = await this.table.query().execute();
449
- let tempCount = 0;
450
- for await (const batch of countResults) {
451
- tempCount += batch.numRows;
452
- }
453
- count = tempCount;
454
- }
455
- }
456
- } catch (countError) {
457
- // If all counting methods fail, mark as unknown (-1)
458
- count = -1;
459
- }
460
-
461
- const stats = {
462
- tableName: this.tableName,
463
- uri: this.uri,
464
- count: count,
465
- isConnected: this.isConnected
466
- };
467
-
468
-
469
- return stats;
470
- });
471
- }
472
-
473
- /**
474
- * Validate a record object
475
- * @private
476
- * @param {Object} record - Record to validate
477
- * @throws {StorageError} If validation fails
478
- */
479
- _validateRecord(record) {
480
- if (!record || typeof record !== 'object') {
481
- throw new StorageError('Record must be an object');
482
- }
483
-
484
- if (!record.id) {
485
- throw new StorageError('Record must have an id field');
486
- }
487
-
488
- if (!record.content) {
489
- throw new StorageError('Record must have a content field');
490
- }
491
-
492
- if (!record.vector) {
493
- throw new StorageError('Record must have a vector field');
494
- }
495
-
496
- this._validateVector(record.vector);
497
- }
498
-
499
- /**
500
- * Validate a vector array
501
- * @private
502
- * @param {Array<number>} vector - Vector to validate
503
- * @throws {QueryError} If validation fails
504
- */
505
- _validateVector(vector) {
506
- if (!Array.isArray(vector)) {
507
- throw new QueryError('Vector must be an array');
508
- }
509
-
510
- // Expected dimension for all-MiniLM-L6-v2 model
511
- const expectedDim = 384;
512
-
513
- if (vector.length !== expectedDim) {
514
- throw new QueryError(
515
- `Vector must have ${expectedDim} dimensions, got ${vector.length}`
516
- );
517
- }
518
-
519
- // Validate all elements are numbers
520
- for (let i = 0; i < vector.length; i++) {
521
- if (typeof vector[i] !== 'number' || isNaN(vector[i])) {
522
- throw new QueryError(`Vector element ${i} is not a valid number`);
523
- }
524
- }
525
- }
526
-
527
- /**
528
- * Sleep for a specified duration
529
- * @private
530
- * @param {number} ms - Milliseconds to sleep
531
- * @returns {Promise<void>}
532
- */
533
- _sleep(ms) {
534
- return new Promise(resolve => setTimeout(resolve, ms));
535
- }
536
-
537
- /**
538
- * Check if an error is retryable (transient network/connection issues)
539
- * @private
540
- * @param {Error} error - Error to check
541
- * @returns {boolean} True if error is retryable
542
- */
543
- _isRetryableError(error) {
544
- if (!error || !error.message) return false;
545
-
546
- const message = error.message.toLowerCase();
547
-
548
- // Network-related errors
549
- const retryablePatterns = [
550
- 'econnreset', // Connection reset by peer
551
- 'etimedout', // Operation timed out
552
- 'enotfound', // DNS resolution failed
553
- 'econnrefused', // Connection refused
554
- 'enetunreach', // Network unreachable
555
- 'ehostunreach', // Host unreachable
556
- 'socket hang up', // Socket closed unexpectedly
557
- 'network error', // Generic network error
558
- 'failed to fetch', // Fetch/network failure
559
- 'timeout', // Timeout occurred
560
- ];
561
-
562
- // Check for network patterns
563
- const hasNetworkPattern = retryablePatterns.some(pattern => message.includes(pattern));
564
-
565
- // Check for 5xx HTTP errors (server-side errors that may be transient)
566
- const hasServerError = /5\d{2}/.test(message);
567
-
568
- // Check for specific LanceDB/lancedb errors that may be transient
569
- const lancedbRetryable = [
570
- 'connection',
571
- 'database closed',
572
- 'table not found',
573
- 'lock',
574
- 'busy',
575
- 'temporary'
576
- ].some(pattern => message.includes(pattern));
577
-
578
- return hasNetworkPattern || hasServerError || lancedbRetryable;
579
- }
580
-
581
- /**
582
- * Retry an operation with exponential backoff
583
- * @private
584
- * @param {Function} operation - Async function to retry
585
- * @param {number} [maxRetries] - Maximum retry attempts (default: 3)
586
- * @param {number} [baseDelay] - Base delay in ms (default: 1000)
587
- * @returns {Promise<*>} Result of the operation
588
- * @throws {Error} If all retries fail, throws the last error
589
- */
590
- async _retryOperation(operation, maxRetries, baseDelay) {
591
- const max = maxRetries ?? this.maxRetries;
592
- const delay = baseDelay ?? this.retryDelay;
593
- let lastError = null;
594
-
595
- for (let attempt = 1; attempt <= max; attempt++) {
596
- try {
597
- return await operation();
598
- } catch (error) {
599
- lastError = error;
600
-
601
- // Check if error is retryable
602
- // @ts-ignore - check error type
603
- if (!this._isRetryableError(error)) {
604
- // Non-retryable error, throw immediately
605
- throw error;
606
- }
607
-
608
- // Check if we've exhausted retries
609
- if (attempt === max) {
610
- throw error;
611
- }
612
-
613
- // Calculate exponential backoff delay (1s, 2s, 4s, etc.)
614
- const backoffMs = delay * Math.pow(2, attempt - 1);
615
-
616
- // Add jitter (0-25% of delay) to prevent thundering herd
617
- const jitterMs = backoffMs * Math.random() * 0.25;
618
-
619
- const message = error instanceof Error ? error.message : String(error);
620
- console.warn(
621
- `[LanceDBClient] Retryable error on attempt ${attempt}/${max}: ${message}. ` +
622
- `Retrying in ${Math.round((backoffMs + jitterMs))}ms...`
623
- );
624
-
625
- await this._sleep(backoffMs + jitterMs);
626
- }
627
- }
628
- // Should not reach here, but just in case
629
- throw lastError;
630
- }}
631
-
632
- export { LanceDBClient };
633
- export default LanceDBClient;