@dudousxd/nestjs-agent-rag 0.14.2 → 0.14.3
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/README.md +4 -2
- package/dist/index.cjs +238 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -13
- package/dist/index.d.ts +33 -13
- package/dist/index.js +238 -115
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -481,8 +481,10 @@ array is refused so a request can't widen a search. A corpus shared by everyone
|
|
|
481
481
|
|
|
482
482
|
**Saving doesn't index.** Embedding is slow and paid for, so call `index` from a job or wherever the
|
|
483
483
|
text changed. It embeds first, so a provider failure keeps the old chunks. On `PgVectorStore` the swap is
|
|
484
|
-
one transaction. On Redis,
|
|
485
|
-
|
|
484
|
+
one transaction. On Redis, it is one script, and concurrent calls wait for each other. A Redis document
|
|
485
|
+
of more than 256 chunks, counting old and new, is written in steps so it doesn't block the server: a
|
|
486
|
+
search can briefly see both versions, but concurrent calls still can't leave a mix behind. On stores
|
|
487
|
+
without `replaceDocument`, it is two statements.
|
|
486
488
|
|
|
487
489
|
**Deleting.** With Drizzle, use `.returning()` and call `remove`. With MikroORM, add
|
|
488
490
|
`provideRagDeleteSync(ProjectDocument, DocumentsRag)` from `@dudousxd/nestjs-agent-store-mikro-orm` to
|
package/dist/index.cjs
CHANGED
|
@@ -295,14 +295,20 @@ var MemoryVectorStore = class {
|
|
|
295
295
|
this.records.set(record.id, record);
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
/**
|
|
298
|
+
/** A replace and a removal of one document take turns, in the order they were called. */
|
|
299
299
|
async replaceDocument(documentId, records) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}).then(async () => {
|
|
303
|
-
await this.remove(documentId);
|
|
300
|
+
await this.inTurn(documentId, async () => {
|
|
301
|
+
this.deleteChunks(documentId);
|
|
304
302
|
await this.upsert(records);
|
|
305
303
|
});
|
|
304
|
+
}
|
|
305
|
+
async remove(documentId) {
|
|
306
|
+
await this.inTurn(documentId, async () => this.deleteChunks(documentId));
|
|
307
|
+
}
|
|
308
|
+
async inTurn(documentId, work) {
|
|
309
|
+
const previous = this.replacing.get(documentId) ?? Promise.resolve();
|
|
310
|
+
const run = previous.catch(() => {
|
|
311
|
+
}).then(work);
|
|
306
312
|
this.replacing.set(documentId, run);
|
|
307
313
|
try {
|
|
308
314
|
await run;
|
|
@@ -312,7 +318,7 @@ var MemoryVectorStore = class {
|
|
|
312
318
|
}
|
|
313
319
|
}
|
|
314
320
|
}
|
|
315
|
-
|
|
321
|
+
deleteChunks(documentId) {
|
|
316
322
|
for (const id of this.records.keys()) {
|
|
317
323
|
if (documentIdOf(id) === documentId) {
|
|
318
324
|
this.records.delete(id);
|
|
@@ -409,12 +415,9 @@ var MemoryVectorStore = class {
|
|
|
409
415
|
if (documentIds.length === 0) {
|
|
410
416
|
return;
|
|
411
417
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
this.records.delete(id);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
+
await Promise.all([
|
|
419
|
+
...new Set(documentIds)
|
|
420
|
+
].map((documentId) => this.remove(documentId)));
|
|
418
421
|
}
|
|
419
422
|
/**
|
|
420
423
|
* See {@link VectorStore.removeWhere}. The empty-array short-circuit below is redundant
|
|
@@ -433,6 +436,7 @@ var MemoryVectorStore = class {
|
|
|
433
436
|
if (filterMatchesNothing(filter)) {
|
|
434
437
|
return 0;
|
|
435
438
|
}
|
|
439
|
+
await Promise.allSettled(this.replacing.values());
|
|
436
440
|
let removed = 0;
|
|
437
441
|
for (const [id, record] of this.records) {
|
|
438
442
|
if (matchesFilter(record.metadata, filter)) {
|
|
@@ -1227,6 +1231,19 @@ var REPLACE_LOCK_TTL_MS = 3e4;
|
|
|
1227
1231
|
var REPLACE_LOCK_WAIT_MS = 12e4;
|
|
1228
1232
|
var RENEW_LOCK_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) end return 0";
|
|
1229
1233
|
var RELEASE_LOCK_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0";
|
|
1234
|
+
var SWAP_CHUNKS_SCRIPT = `
|
|
1235
|
+
if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
1236
|
+
local stale = tonumber(ARGV[2])
|
|
1237
|
+
for i = 2, stale + 1 do redis.call('DEL', KEYS[i]) end
|
|
1238
|
+
local arg = 3
|
|
1239
|
+
for i = stale + 2, #KEYS do
|
|
1240
|
+
local fields = tonumber(ARGV[arg])
|
|
1241
|
+
redis.call('DEL', KEYS[i])
|
|
1242
|
+
redis.call('HSET', KEYS[i], unpack(ARGV, arg + 1, arg + fields))
|
|
1243
|
+
arg = arg + fields + 1
|
|
1244
|
+
end
|
|
1245
|
+
return 1`;
|
|
1246
|
+
var SWAP_MAX_CHUNKS = 256;
|
|
1230
1247
|
var RedisVectorSchemaMismatchError = class extends Error {
|
|
1231
1248
|
static {
|
|
1232
1249
|
__name(this, "RedisVectorSchemaMismatchError");
|
|
@@ -1364,42 +1381,56 @@ var RedisVectorStore = class {
|
|
|
1364
1381
|
}
|
|
1365
1382
|
async upsert(records) {
|
|
1366
1383
|
for (const record of records) {
|
|
1367
|
-
|
|
1384
|
+
await this.client.sendCommand([
|
|
1368
1385
|
"HSET",
|
|
1369
1386
|
`${this.prefix}${record.id}`,
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1387
|
+
...this.chunkFields(record)
|
|
1388
|
+
]);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
chunkFields(record) {
|
|
1392
|
+
const fields = [
|
|
1393
|
+
"text",
|
|
1394
|
+
record.text,
|
|
1395
|
+
"embedding",
|
|
1396
|
+
encodeVector(record.embedding)
|
|
1397
|
+
];
|
|
1398
|
+
if (record.source !== void 0) {
|
|
1399
|
+
fields.push("source", record.source);
|
|
1400
|
+
}
|
|
1401
|
+
if (record.metadata !== void 0) {
|
|
1402
|
+
fields.push("metadata_json", JSON.stringify(record.metadata));
|
|
1403
|
+
for (const field of this.filterableFields) {
|
|
1404
|
+
const value = record.metadata[field];
|
|
1405
|
+
if (value !== void 0) {
|
|
1406
|
+
fields.push(`meta_${field}`, encodeTag(value));
|
|
1385
1407
|
}
|
|
1386
1408
|
}
|
|
1387
|
-
await this.client.sendCommand(args);
|
|
1388
1409
|
}
|
|
1410
|
+
return fields;
|
|
1389
1411
|
}
|
|
1390
1412
|
/**
|
|
1391
|
-
* Replaces a document's chunks, one writer at a time per document.
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1413
|
+
* Replaces a document's chunks, one writer at a time per document. The swap is one script, so a search
|
|
1414
|
+
* sees the old version or the new one, and a crash leaves the old one. The keys to drop are found
|
|
1415
|
+
* first, under the lock, because a script can't scan the keyspace.
|
|
1416
|
+
*
|
|
1417
|
+
* A document past {@link SWAP_MAX_CHUNKS} would block Redis for too long that way. Its new chunks go in
|
|
1418
|
+
* first and the old ones come out after, so a search may briefly see both, and a crash leaves the
|
|
1419
|
+
* document searchable for the next call to repair.
|
|
1395
1420
|
*/
|
|
1396
1421
|
async replaceDocument(documentId, records) {
|
|
1397
|
-
await this.
|
|
1398
|
-
|
|
1399
|
-
|
|
1422
|
+
await this.withDocumentLocks([
|
|
1423
|
+
documentId
|
|
1424
|
+
], async (assertHeld, token) => {
|
|
1400
1425
|
const current = new Set(records.map((record) => `${this.prefix}${record.id}`));
|
|
1401
1426
|
const stale = (await this.chunkKeys(documentId)).filter((key) => !current.has(key));
|
|
1402
1427
|
assertHeld();
|
|
1428
|
+
if (records.length + stale.length <= SWAP_MAX_CHUNKS) {
|
|
1429
|
+
await this.swapChunks(documentId, token, stale, records);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
await this.upsert(records);
|
|
1433
|
+
assertHeld();
|
|
1403
1434
|
if (stale.length > 0) {
|
|
1404
1435
|
await this.client.sendCommand([
|
|
1405
1436
|
"DEL",
|
|
@@ -1408,69 +1439,110 @@ var RedisVectorStore = class {
|
|
|
1408
1439
|
}
|
|
1409
1440
|
});
|
|
1410
1441
|
}
|
|
1442
|
+
async swapChunks(documentId, token, stale, records) {
|
|
1443
|
+
const keys = [
|
|
1444
|
+
this.lockKey(documentId),
|
|
1445
|
+
...stale,
|
|
1446
|
+
...records.map((record) => `${this.prefix}${record.id}`)
|
|
1447
|
+
];
|
|
1448
|
+
const args = [
|
|
1449
|
+
token,
|
|
1450
|
+
String(stale.length)
|
|
1451
|
+
];
|
|
1452
|
+
for (const record of records) {
|
|
1453
|
+
const fields = this.chunkFields(record);
|
|
1454
|
+
args.push(String(fields.length), ...fields);
|
|
1455
|
+
}
|
|
1456
|
+
const swapped = await this.client.sendCommand([
|
|
1457
|
+
"EVAL",
|
|
1458
|
+
SWAP_CHUNKS_SCRIPT,
|
|
1459
|
+
String(keys.length),
|
|
1460
|
+
...keys,
|
|
1461
|
+
...args
|
|
1462
|
+
]);
|
|
1463
|
+
if (Number(swapped) === 0) {
|
|
1464
|
+
throw lostLockError(documentId);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
lockKey(documentId) {
|
|
1468
|
+
return `${this.index}:replace-lock:${documentId}`;
|
|
1469
|
+
}
|
|
1411
1470
|
/**
|
|
1412
|
-
* A lease, so a crashed writer can't block
|
|
1413
|
-
* which keeps a large document from outliving
|
|
1471
|
+
* A lease on each document, so a crashed writer can't block one forever. They are renewed while the
|
|
1472
|
+
* work runs, which keeps a large document from outliving them.
|
|
1473
|
+
*
|
|
1474
|
+
* Several documents are locked in sorted order, without duplicates. Every other caller either holds
|
|
1475
|
+
* one lock or takes them in that same order, so no two calls can wait on each other.
|
|
1414
1476
|
*/
|
|
1415
|
-
async
|
|
1416
|
-
const key = `${this.index}:replace-lock:${documentId}`;
|
|
1477
|
+
async withDocumentLocks(documentIds, work) {
|
|
1417
1478
|
const token = (0, import_node_crypto.randomUUID)();
|
|
1418
|
-
const
|
|
1419
|
-
let
|
|
1420
|
-
while (await this.client.sendCommand([
|
|
1421
|
-
"SET",
|
|
1422
|
-
key,
|
|
1423
|
-
token,
|
|
1424
|
-
"NX",
|
|
1425
|
-
"PX",
|
|
1426
|
-
String(REPLACE_LOCK_TTL_MS)
|
|
1427
|
-
]) === null) {
|
|
1428
|
-
if (Date.now() >= deadline) {
|
|
1429
|
-
throw new Error(`RedisVectorStore: timed out after ${REPLACE_LOCK_WAIT_MS}ms waiting to change document "${documentId}"`);
|
|
1430
|
-
}
|
|
1431
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1432
|
-
delay = Math.min(delay * 2, 250);
|
|
1433
|
-
}
|
|
1434
|
-
let lost = false;
|
|
1479
|
+
const held = [];
|
|
1480
|
+
let lostDocument;
|
|
1435
1481
|
const assertHeld = /* @__PURE__ */ __name(() => {
|
|
1436
|
-
if (
|
|
1437
|
-
throw
|
|
1482
|
+
if (lostDocument !== void 0) {
|
|
1483
|
+
throw lostLockError(lostDocument);
|
|
1438
1484
|
}
|
|
1439
1485
|
}, "assertHeld");
|
|
1440
1486
|
const renewal = setInterval(() => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
(
|
|
1450
|
-
|
|
1451
|
-
|
|
1487
|
+
for (const key of held) {
|
|
1488
|
+
void Promise.resolve(this.client.sendCommand([
|
|
1489
|
+
"EVAL",
|
|
1490
|
+
RENEW_LOCK_SCRIPT,
|
|
1491
|
+
"1",
|
|
1492
|
+
key,
|
|
1493
|
+
token,
|
|
1494
|
+
String(REPLACE_LOCK_TTL_MS)
|
|
1495
|
+
])).then(
|
|
1496
|
+
(renewed) => {
|
|
1497
|
+
if (Number(renewed) === 0) {
|
|
1498
|
+
lostDocument = key.slice(this.lockKey("").length);
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
// A failed renewal isn't a lost lock yet: the lease may still have time left.
|
|
1502
|
+
() => {
|
|
1452
1503
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
() => {
|
|
1456
|
-
}
|
|
1457
|
-
);
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1458
1506
|
}, REPLACE_LOCK_TTL_MS / 3);
|
|
1459
1507
|
renewal.unref?.();
|
|
1460
1508
|
try {
|
|
1461
|
-
const
|
|
1509
|
+
for (const documentId of [
|
|
1510
|
+
...new Set(documentIds)
|
|
1511
|
+
].sort()) {
|
|
1512
|
+
await this.acquireLock(documentId, token);
|
|
1513
|
+
held.push(this.lockKey(documentId));
|
|
1514
|
+
}
|
|
1515
|
+
const result = await work(assertHeld, token);
|
|
1462
1516
|
assertHeld();
|
|
1463
1517
|
return result;
|
|
1464
1518
|
} finally {
|
|
1465
1519
|
clearInterval(renewal);
|
|
1466
|
-
await Promise.resolve(this.client.sendCommand([
|
|
1520
|
+
await Promise.all(held.map((key) => Promise.resolve(this.client.sendCommand([
|
|
1467
1521
|
"EVAL",
|
|
1468
1522
|
RELEASE_LOCK_SCRIPT,
|
|
1469
1523
|
"1",
|
|
1470
1524
|
key,
|
|
1471
1525
|
token
|
|
1472
1526
|
])).catch(() => {
|
|
1473
|
-
});
|
|
1527
|
+
})));
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
async acquireLock(documentId, token) {
|
|
1531
|
+
const deadline = Date.now() + REPLACE_LOCK_WAIT_MS;
|
|
1532
|
+
let delay = 10;
|
|
1533
|
+
while (await this.client.sendCommand([
|
|
1534
|
+
"SET",
|
|
1535
|
+
this.lockKey(documentId),
|
|
1536
|
+
token,
|
|
1537
|
+
"NX",
|
|
1538
|
+
"PX",
|
|
1539
|
+
String(REPLACE_LOCK_TTL_MS)
|
|
1540
|
+
]) === null) {
|
|
1541
|
+
if (Date.now() >= deadline) {
|
|
1542
|
+
throw new Error(`RedisVectorStore: timed out after ${REPLACE_LOCK_WAIT_MS}ms waiting to change document "${documentId}"`);
|
|
1543
|
+
}
|
|
1544
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1545
|
+
delay = Math.min(delay * 2, 250);
|
|
1474
1546
|
}
|
|
1475
1547
|
}
|
|
1476
1548
|
/**
|
|
@@ -1494,7 +1566,9 @@ var RedisVectorStore = class {
|
|
|
1494
1566
|
* than N scans). Neither depends on a field that older chunks lack, so neither has a migration.
|
|
1495
1567
|
*/
|
|
1496
1568
|
async remove(documentId) {
|
|
1497
|
-
await this.
|
|
1569
|
+
await this.withDocumentLocks([
|
|
1570
|
+
documentId
|
|
1571
|
+
], async () => {
|
|
1498
1572
|
const keys = await this.chunkKeys(documentId);
|
|
1499
1573
|
if (keys.length > 0) {
|
|
1500
1574
|
await this.client.sendCommand([
|
|
@@ -1759,44 +1833,51 @@ var RedisVectorStore = class {
|
|
|
1759
1833
|
*
|
|
1760
1834
|
* It deletes by key, exactly as `remove` does, so it is correct on chunks written by any version of
|
|
1761
1835
|
* this library — there is no field it needs the corpus to already carry.
|
|
1836
|
+
*
|
|
1837
|
+
* It holds every document's lock for the whole scan, so it waits for a `replaceDocument` in progress
|
|
1838
|
+
* and one can't start partway through. That adds two commands per document to the one scan.
|
|
1762
1839
|
*/
|
|
1763
1840
|
async removeMany(documentIds) {
|
|
1764
1841
|
if (documentIds.length === 0) {
|
|
1765
1842
|
return;
|
|
1766
1843
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1844
|
+
await this.withDocumentLocks(documentIds, async (assertHeld) => {
|
|
1845
|
+
const wanted = new Set(documentIds);
|
|
1846
|
+
let pending = documentIds.map((id) => `${this.prefix}${id}`);
|
|
1847
|
+
let cursor = "0";
|
|
1848
|
+
do {
|
|
1849
|
+
const reply = await this.client.sendCommand([
|
|
1850
|
+
"SCAN",
|
|
1851
|
+
cursor,
|
|
1852
|
+
"MATCH",
|
|
1853
|
+
`${escapeGlob(this.prefix)}*`,
|
|
1854
|
+
"COUNT",
|
|
1855
|
+
String(SCAN_PAGE)
|
|
1856
|
+
]);
|
|
1857
|
+
const page = parseScanReply(reply);
|
|
1858
|
+
cursor = page.cursor;
|
|
1859
|
+
for (const key of page.keys) {
|
|
1860
|
+
if (wanted.has(documentIdOf(stripPrefix(key, this.prefix)))) {
|
|
1861
|
+
pending.push(key);
|
|
1862
|
+
}
|
|
1784
1863
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1864
|
+
if (pending.length >= DELETE_BATCH) {
|
|
1865
|
+
assertHeld();
|
|
1866
|
+
await this.client.sendCommand([
|
|
1867
|
+
"DEL",
|
|
1868
|
+
...pending
|
|
1869
|
+
]);
|
|
1870
|
+
pending = [];
|
|
1871
|
+
}
|
|
1872
|
+
} while (cursor !== "0");
|
|
1873
|
+
if (pending.length > 0) {
|
|
1874
|
+
assertHeld();
|
|
1787
1875
|
await this.client.sendCommand([
|
|
1788
1876
|
"DEL",
|
|
1789
1877
|
...pending
|
|
1790
1878
|
]);
|
|
1791
|
-
pending = [];
|
|
1792
1879
|
}
|
|
1793
|
-
}
|
|
1794
|
-
if (pending.length > 0) {
|
|
1795
|
-
await this.client.sendCommand([
|
|
1796
|
-
"DEL",
|
|
1797
|
-
...pending
|
|
1798
|
-
]);
|
|
1799
|
-
}
|
|
1880
|
+
});
|
|
1800
1881
|
}
|
|
1801
1882
|
/**
|
|
1802
1883
|
* Delete every chunk matching `filter`, via `FT.SEARCH … NOCONTENT` + `DEL` — no keyspace scan at
|
|
@@ -1821,6 +1902,10 @@ var RedisVectorStore = class {
|
|
|
1821
1902
|
* from the index, so paging forward would skip their replacements. If a page comes back non-empty
|
|
1822
1903
|
* but `DEL` reports nothing removed (keys expired between query and delete, or the index is stale),
|
|
1823
1904
|
* the loop stops rather than spinning on results it can never consume.
|
|
1905
|
+
*
|
|
1906
|
+
* The documents aren't known until a page comes back, so each page locks the documents it touches, one
|
|
1907
|
+
* page at a time, and asks again with `INKEYS` once it holds them. A `replaceDocument` that finished in
|
|
1908
|
+
* between may have dropped those chunks, or rewritten them to no longer match.
|
|
1824
1909
|
*/
|
|
1825
1910
|
async removeWhere(filter) {
|
|
1826
1911
|
const keys = Object.keys(filter);
|
|
@@ -1842,6 +1927,7 @@ var RedisVectorStore = class {
|
|
|
1842
1927
|
}
|
|
1843
1928
|
const query = buildFilter(filter);
|
|
1844
1929
|
let removed = 0;
|
|
1930
|
+
let emptyPageBefore = false;
|
|
1845
1931
|
for (; ; ) {
|
|
1846
1932
|
const reply = await this.client.sendCommand([
|
|
1847
1933
|
"FT.SEARCH",
|
|
@@ -1858,14 +1944,46 @@ var RedisVectorStore = class {
|
|
|
1858
1944
|
if (page.length === 0) {
|
|
1859
1945
|
break;
|
|
1860
1946
|
}
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1947
|
+
const documents = page.map((key) => documentIdOf(stripPrefix(key, this.prefix)));
|
|
1948
|
+
const outcome = await this.withDocumentLocks(documents, async (assertHeld) => {
|
|
1949
|
+
const confirmed = parseSearchKeys(await this.client.sendCommand([
|
|
1950
|
+
"FT.SEARCH",
|
|
1951
|
+
this.index,
|
|
1952
|
+
query,
|
|
1953
|
+
"NOCONTENT",
|
|
1954
|
+
"INKEYS",
|
|
1955
|
+
String(page.length),
|
|
1956
|
+
...page,
|
|
1957
|
+
"LIMIT",
|
|
1958
|
+
"0",
|
|
1959
|
+
String(DELETE_BATCH),
|
|
1960
|
+
"DIALECT",
|
|
1961
|
+
"2"
|
|
1962
|
+
]));
|
|
1963
|
+
assertHeld();
|
|
1964
|
+
return confirmed.length === 0 ? {
|
|
1965
|
+
confirmed: 0,
|
|
1966
|
+
deleted: 0
|
|
1967
|
+
} : {
|
|
1968
|
+
confirmed: confirmed.length,
|
|
1969
|
+
deleted: Number(await this.client.sendCommand([
|
|
1970
|
+
"DEL",
|
|
1971
|
+
...confirmed
|
|
1972
|
+
]))
|
|
1973
|
+
};
|
|
1974
|
+
});
|
|
1975
|
+
if (outcome.confirmed === 0) {
|
|
1976
|
+
if (emptyPageBefore) {
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
emptyPageBefore = true;
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
emptyPageBefore = false;
|
|
1983
|
+
if (!Number.isFinite(outcome.deleted) || outcome.deleted === 0) {
|
|
1866
1984
|
break;
|
|
1867
1985
|
}
|
|
1868
|
-
removed += deleted;
|
|
1986
|
+
removed += outcome.deleted;
|
|
1869
1987
|
}
|
|
1870
1988
|
return removed;
|
|
1871
1989
|
}
|
|
@@ -1963,6 +2081,10 @@ var RedisVectorStore = class {
|
|
|
1963
2081
|
return parseSearchReply(reply, this.prefix, "lexical");
|
|
1964
2082
|
}
|
|
1965
2083
|
};
|
|
2084
|
+
function lostLockError(documentId) {
|
|
2085
|
+
return new Error(`RedisVectorStore: lost the lock while changing document "${documentId}"; call again to repair it`);
|
|
2086
|
+
}
|
|
2087
|
+
__name(lostLockError, "lostLockError");
|
|
1966
2088
|
var SCAN_PAGE = 1e3;
|
|
1967
2089
|
var DELETE_BATCH = 512;
|
|
1968
2090
|
function stripPrefix(key, prefix) {
|
|
@@ -3029,9 +3151,10 @@ var RagIndex = class {
|
|
|
3029
3151
|
}
|
|
3030
3152
|
/**
|
|
3031
3153
|
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
3032
|
-
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres
|
|
3033
|
-
* adds the new chunks before dropping the old, so
|
|
3034
|
-
* `replaceDocument`, a crash mid-swap loses the
|
|
3154
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres and one
|
|
3155
|
+
* script on Redis, where a document too large for that adds the new chunks before dropping the old, so
|
|
3156
|
+
* a search can briefly see both. On a store without `replaceDocument`, a crash mid-swap loses the
|
|
3157
|
+
* document until the next `index`.
|
|
3035
3158
|
*/
|
|
3036
3159
|
async index(row) {
|
|
3037
3160
|
const { definition, deps } = this;
|