@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/dist/index.js
CHANGED
|
@@ -207,14 +207,20 @@ var MemoryVectorStore = class {
|
|
|
207
207
|
this.records.set(record.id, record);
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
/**
|
|
210
|
+
/** A replace and a removal of one document take turns, in the order they were called. */
|
|
211
211
|
async replaceDocument(documentId, records) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}).then(async () => {
|
|
215
|
-
await this.remove(documentId);
|
|
212
|
+
await this.inTurn(documentId, async () => {
|
|
213
|
+
this.deleteChunks(documentId);
|
|
216
214
|
await this.upsert(records);
|
|
217
215
|
});
|
|
216
|
+
}
|
|
217
|
+
async remove(documentId) {
|
|
218
|
+
await this.inTurn(documentId, async () => this.deleteChunks(documentId));
|
|
219
|
+
}
|
|
220
|
+
async inTurn(documentId, work) {
|
|
221
|
+
const previous = this.replacing.get(documentId) ?? Promise.resolve();
|
|
222
|
+
const run = previous.catch(() => {
|
|
223
|
+
}).then(work);
|
|
218
224
|
this.replacing.set(documentId, run);
|
|
219
225
|
try {
|
|
220
226
|
await run;
|
|
@@ -224,7 +230,7 @@ var MemoryVectorStore = class {
|
|
|
224
230
|
}
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
|
-
|
|
233
|
+
deleteChunks(documentId) {
|
|
228
234
|
for (const id of this.records.keys()) {
|
|
229
235
|
if (documentIdOf(id) === documentId) {
|
|
230
236
|
this.records.delete(id);
|
|
@@ -321,12 +327,9 @@ var MemoryVectorStore = class {
|
|
|
321
327
|
if (documentIds.length === 0) {
|
|
322
328
|
return;
|
|
323
329
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
this.records.delete(id);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
+
await Promise.all([
|
|
331
|
+
...new Set(documentIds)
|
|
332
|
+
].map((documentId) => this.remove(documentId)));
|
|
330
333
|
}
|
|
331
334
|
/**
|
|
332
335
|
* See {@link VectorStore.removeWhere}. The empty-array short-circuit below is redundant
|
|
@@ -345,6 +348,7 @@ var MemoryVectorStore = class {
|
|
|
345
348
|
if (filterMatchesNothing(filter)) {
|
|
346
349
|
return 0;
|
|
347
350
|
}
|
|
351
|
+
await Promise.allSettled(this.replacing.values());
|
|
348
352
|
let removed = 0;
|
|
349
353
|
for (const [id, record] of this.records) {
|
|
350
354
|
if (matchesFilter(record.metadata, filter)) {
|
|
@@ -1139,6 +1143,19 @@ var REPLACE_LOCK_TTL_MS = 3e4;
|
|
|
1139
1143
|
var REPLACE_LOCK_WAIT_MS = 12e4;
|
|
1140
1144
|
var RENEW_LOCK_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) end return 0";
|
|
1141
1145
|
var RELEASE_LOCK_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0";
|
|
1146
|
+
var SWAP_CHUNKS_SCRIPT = `
|
|
1147
|
+
if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
1148
|
+
local stale = tonumber(ARGV[2])
|
|
1149
|
+
for i = 2, stale + 1 do redis.call('DEL', KEYS[i]) end
|
|
1150
|
+
local arg = 3
|
|
1151
|
+
for i = stale + 2, #KEYS do
|
|
1152
|
+
local fields = tonumber(ARGV[arg])
|
|
1153
|
+
redis.call('DEL', KEYS[i])
|
|
1154
|
+
redis.call('HSET', KEYS[i], unpack(ARGV, arg + 1, arg + fields))
|
|
1155
|
+
arg = arg + fields + 1
|
|
1156
|
+
end
|
|
1157
|
+
return 1`;
|
|
1158
|
+
var SWAP_MAX_CHUNKS = 256;
|
|
1142
1159
|
var RedisVectorSchemaMismatchError = class extends Error {
|
|
1143
1160
|
static {
|
|
1144
1161
|
__name(this, "RedisVectorSchemaMismatchError");
|
|
@@ -1276,42 +1293,56 @@ var RedisVectorStore = class {
|
|
|
1276
1293
|
}
|
|
1277
1294
|
async upsert(records) {
|
|
1278
1295
|
for (const record of records) {
|
|
1279
|
-
|
|
1296
|
+
await this.client.sendCommand([
|
|
1280
1297
|
"HSET",
|
|
1281
1298
|
`${this.prefix}${record.id}`,
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1299
|
+
...this.chunkFields(record)
|
|
1300
|
+
]);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
chunkFields(record) {
|
|
1304
|
+
const fields = [
|
|
1305
|
+
"text",
|
|
1306
|
+
record.text,
|
|
1307
|
+
"embedding",
|
|
1308
|
+
encodeVector(record.embedding)
|
|
1309
|
+
];
|
|
1310
|
+
if (record.source !== void 0) {
|
|
1311
|
+
fields.push("source", record.source);
|
|
1312
|
+
}
|
|
1313
|
+
if (record.metadata !== void 0) {
|
|
1314
|
+
fields.push("metadata_json", JSON.stringify(record.metadata));
|
|
1315
|
+
for (const field of this.filterableFields) {
|
|
1316
|
+
const value = record.metadata[field];
|
|
1317
|
+
if (value !== void 0) {
|
|
1318
|
+
fields.push(`meta_${field}`, encodeTag(value));
|
|
1297
1319
|
}
|
|
1298
1320
|
}
|
|
1299
|
-
await this.client.sendCommand(args);
|
|
1300
1321
|
}
|
|
1322
|
+
return fields;
|
|
1301
1323
|
}
|
|
1302
1324
|
/**
|
|
1303
|
-
* Replaces a document's chunks, one writer at a time per document.
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1325
|
+
* Replaces a document's chunks, one writer at a time per document. The swap is one script, so a search
|
|
1326
|
+
* sees the old version or the new one, and a crash leaves the old one. The keys to drop are found
|
|
1327
|
+
* first, under the lock, because a script can't scan the keyspace.
|
|
1328
|
+
*
|
|
1329
|
+
* A document past {@link SWAP_MAX_CHUNKS} would block Redis for too long that way. Its new chunks go in
|
|
1330
|
+
* first and the old ones come out after, so a search may briefly see both, and a crash leaves the
|
|
1331
|
+
* document searchable for the next call to repair.
|
|
1307
1332
|
*/
|
|
1308
1333
|
async replaceDocument(documentId, records) {
|
|
1309
|
-
await this.
|
|
1310
|
-
|
|
1311
|
-
|
|
1334
|
+
await this.withDocumentLocks([
|
|
1335
|
+
documentId
|
|
1336
|
+
], async (assertHeld, token) => {
|
|
1312
1337
|
const current = new Set(records.map((record) => `${this.prefix}${record.id}`));
|
|
1313
1338
|
const stale = (await this.chunkKeys(documentId)).filter((key) => !current.has(key));
|
|
1314
1339
|
assertHeld();
|
|
1340
|
+
if (records.length + stale.length <= SWAP_MAX_CHUNKS) {
|
|
1341
|
+
await this.swapChunks(documentId, token, stale, records);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
await this.upsert(records);
|
|
1345
|
+
assertHeld();
|
|
1315
1346
|
if (stale.length > 0) {
|
|
1316
1347
|
await this.client.sendCommand([
|
|
1317
1348
|
"DEL",
|
|
@@ -1320,69 +1351,110 @@ var RedisVectorStore = class {
|
|
|
1320
1351
|
}
|
|
1321
1352
|
});
|
|
1322
1353
|
}
|
|
1354
|
+
async swapChunks(documentId, token, stale, records) {
|
|
1355
|
+
const keys = [
|
|
1356
|
+
this.lockKey(documentId),
|
|
1357
|
+
...stale,
|
|
1358
|
+
...records.map((record) => `${this.prefix}${record.id}`)
|
|
1359
|
+
];
|
|
1360
|
+
const args = [
|
|
1361
|
+
token,
|
|
1362
|
+
String(stale.length)
|
|
1363
|
+
];
|
|
1364
|
+
for (const record of records) {
|
|
1365
|
+
const fields = this.chunkFields(record);
|
|
1366
|
+
args.push(String(fields.length), ...fields);
|
|
1367
|
+
}
|
|
1368
|
+
const swapped = await this.client.sendCommand([
|
|
1369
|
+
"EVAL",
|
|
1370
|
+
SWAP_CHUNKS_SCRIPT,
|
|
1371
|
+
String(keys.length),
|
|
1372
|
+
...keys,
|
|
1373
|
+
...args
|
|
1374
|
+
]);
|
|
1375
|
+
if (Number(swapped) === 0) {
|
|
1376
|
+
throw lostLockError(documentId);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
lockKey(documentId) {
|
|
1380
|
+
return `${this.index}:replace-lock:${documentId}`;
|
|
1381
|
+
}
|
|
1323
1382
|
/**
|
|
1324
|
-
* A lease, so a crashed writer can't block
|
|
1325
|
-
* which keeps a large document from outliving
|
|
1383
|
+
* A lease on each document, so a crashed writer can't block one forever. They are renewed while the
|
|
1384
|
+
* work runs, which keeps a large document from outliving them.
|
|
1385
|
+
*
|
|
1386
|
+
* Several documents are locked in sorted order, without duplicates. Every other caller either holds
|
|
1387
|
+
* one lock or takes them in that same order, so no two calls can wait on each other.
|
|
1326
1388
|
*/
|
|
1327
|
-
async
|
|
1328
|
-
const key = `${this.index}:replace-lock:${documentId}`;
|
|
1389
|
+
async withDocumentLocks(documentIds, work) {
|
|
1329
1390
|
const token = randomUUID();
|
|
1330
|
-
const
|
|
1331
|
-
let
|
|
1332
|
-
while (await this.client.sendCommand([
|
|
1333
|
-
"SET",
|
|
1334
|
-
key,
|
|
1335
|
-
token,
|
|
1336
|
-
"NX",
|
|
1337
|
-
"PX",
|
|
1338
|
-
String(REPLACE_LOCK_TTL_MS)
|
|
1339
|
-
]) === null) {
|
|
1340
|
-
if (Date.now() >= deadline) {
|
|
1341
|
-
throw new Error(`RedisVectorStore: timed out after ${REPLACE_LOCK_WAIT_MS}ms waiting to change document "${documentId}"`);
|
|
1342
|
-
}
|
|
1343
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1344
|
-
delay = Math.min(delay * 2, 250);
|
|
1345
|
-
}
|
|
1346
|
-
let lost = false;
|
|
1391
|
+
const held = [];
|
|
1392
|
+
let lostDocument;
|
|
1347
1393
|
const assertHeld = /* @__PURE__ */ __name(() => {
|
|
1348
|
-
if (
|
|
1349
|
-
throw
|
|
1394
|
+
if (lostDocument !== void 0) {
|
|
1395
|
+
throw lostLockError(lostDocument);
|
|
1350
1396
|
}
|
|
1351
1397
|
}, "assertHeld");
|
|
1352
1398
|
const renewal = setInterval(() => {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
(
|
|
1362
|
-
|
|
1363
|
-
|
|
1399
|
+
for (const key of held) {
|
|
1400
|
+
void Promise.resolve(this.client.sendCommand([
|
|
1401
|
+
"EVAL",
|
|
1402
|
+
RENEW_LOCK_SCRIPT,
|
|
1403
|
+
"1",
|
|
1404
|
+
key,
|
|
1405
|
+
token,
|
|
1406
|
+
String(REPLACE_LOCK_TTL_MS)
|
|
1407
|
+
])).then(
|
|
1408
|
+
(renewed) => {
|
|
1409
|
+
if (Number(renewed) === 0) {
|
|
1410
|
+
lostDocument = key.slice(this.lockKey("").length);
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
// A failed renewal isn't a lost lock yet: the lease may still have time left.
|
|
1414
|
+
() => {
|
|
1364
1415
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
() => {
|
|
1368
|
-
}
|
|
1369
|
-
);
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1370
1418
|
}, REPLACE_LOCK_TTL_MS / 3);
|
|
1371
1419
|
renewal.unref?.();
|
|
1372
1420
|
try {
|
|
1373
|
-
const
|
|
1421
|
+
for (const documentId of [
|
|
1422
|
+
...new Set(documentIds)
|
|
1423
|
+
].sort()) {
|
|
1424
|
+
await this.acquireLock(documentId, token);
|
|
1425
|
+
held.push(this.lockKey(documentId));
|
|
1426
|
+
}
|
|
1427
|
+
const result = await work(assertHeld, token);
|
|
1374
1428
|
assertHeld();
|
|
1375
1429
|
return result;
|
|
1376
1430
|
} finally {
|
|
1377
1431
|
clearInterval(renewal);
|
|
1378
|
-
await Promise.resolve(this.client.sendCommand([
|
|
1432
|
+
await Promise.all(held.map((key) => Promise.resolve(this.client.sendCommand([
|
|
1379
1433
|
"EVAL",
|
|
1380
1434
|
RELEASE_LOCK_SCRIPT,
|
|
1381
1435
|
"1",
|
|
1382
1436
|
key,
|
|
1383
1437
|
token
|
|
1384
1438
|
])).catch(() => {
|
|
1385
|
-
});
|
|
1439
|
+
})));
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
async acquireLock(documentId, token) {
|
|
1443
|
+
const deadline = Date.now() + REPLACE_LOCK_WAIT_MS;
|
|
1444
|
+
let delay = 10;
|
|
1445
|
+
while (await this.client.sendCommand([
|
|
1446
|
+
"SET",
|
|
1447
|
+
this.lockKey(documentId),
|
|
1448
|
+
token,
|
|
1449
|
+
"NX",
|
|
1450
|
+
"PX",
|
|
1451
|
+
String(REPLACE_LOCK_TTL_MS)
|
|
1452
|
+
]) === null) {
|
|
1453
|
+
if (Date.now() >= deadline) {
|
|
1454
|
+
throw new Error(`RedisVectorStore: timed out after ${REPLACE_LOCK_WAIT_MS}ms waiting to change document "${documentId}"`);
|
|
1455
|
+
}
|
|
1456
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1457
|
+
delay = Math.min(delay * 2, 250);
|
|
1386
1458
|
}
|
|
1387
1459
|
}
|
|
1388
1460
|
/**
|
|
@@ -1406,7 +1478,9 @@ var RedisVectorStore = class {
|
|
|
1406
1478
|
* than N scans). Neither depends on a field that older chunks lack, so neither has a migration.
|
|
1407
1479
|
*/
|
|
1408
1480
|
async remove(documentId) {
|
|
1409
|
-
await this.
|
|
1481
|
+
await this.withDocumentLocks([
|
|
1482
|
+
documentId
|
|
1483
|
+
], async () => {
|
|
1410
1484
|
const keys = await this.chunkKeys(documentId);
|
|
1411
1485
|
if (keys.length > 0) {
|
|
1412
1486
|
await this.client.sendCommand([
|
|
@@ -1671,44 +1745,51 @@ var RedisVectorStore = class {
|
|
|
1671
1745
|
*
|
|
1672
1746
|
* It deletes by key, exactly as `remove` does, so it is correct on chunks written by any version of
|
|
1673
1747
|
* this library — there is no field it needs the corpus to already carry.
|
|
1748
|
+
*
|
|
1749
|
+
* It holds every document's lock for the whole scan, so it waits for a `replaceDocument` in progress
|
|
1750
|
+
* and one can't start partway through. That adds two commands per document to the one scan.
|
|
1674
1751
|
*/
|
|
1675
1752
|
async removeMany(documentIds) {
|
|
1676
1753
|
if (documentIds.length === 0) {
|
|
1677
1754
|
return;
|
|
1678
1755
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1756
|
+
await this.withDocumentLocks(documentIds, async (assertHeld) => {
|
|
1757
|
+
const wanted = new Set(documentIds);
|
|
1758
|
+
let pending = documentIds.map((id) => `${this.prefix}${id}`);
|
|
1759
|
+
let cursor = "0";
|
|
1760
|
+
do {
|
|
1761
|
+
const reply = await this.client.sendCommand([
|
|
1762
|
+
"SCAN",
|
|
1763
|
+
cursor,
|
|
1764
|
+
"MATCH",
|
|
1765
|
+
`${escapeGlob(this.prefix)}*`,
|
|
1766
|
+
"COUNT",
|
|
1767
|
+
String(SCAN_PAGE)
|
|
1768
|
+
]);
|
|
1769
|
+
const page = parseScanReply(reply);
|
|
1770
|
+
cursor = page.cursor;
|
|
1771
|
+
for (const key of page.keys) {
|
|
1772
|
+
if (wanted.has(documentIdOf(stripPrefix(key, this.prefix)))) {
|
|
1773
|
+
pending.push(key);
|
|
1774
|
+
}
|
|
1696
1775
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1776
|
+
if (pending.length >= DELETE_BATCH) {
|
|
1777
|
+
assertHeld();
|
|
1778
|
+
await this.client.sendCommand([
|
|
1779
|
+
"DEL",
|
|
1780
|
+
...pending
|
|
1781
|
+
]);
|
|
1782
|
+
pending = [];
|
|
1783
|
+
}
|
|
1784
|
+
} while (cursor !== "0");
|
|
1785
|
+
if (pending.length > 0) {
|
|
1786
|
+
assertHeld();
|
|
1699
1787
|
await this.client.sendCommand([
|
|
1700
1788
|
"DEL",
|
|
1701
1789
|
...pending
|
|
1702
1790
|
]);
|
|
1703
|
-
pending = [];
|
|
1704
1791
|
}
|
|
1705
|
-
}
|
|
1706
|
-
if (pending.length > 0) {
|
|
1707
|
-
await this.client.sendCommand([
|
|
1708
|
-
"DEL",
|
|
1709
|
-
...pending
|
|
1710
|
-
]);
|
|
1711
|
-
}
|
|
1792
|
+
});
|
|
1712
1793
|
}
|
|
1713
1794
|
/**
|
|
1714
1795
|
* Delete every chunk matching `filter`, via `FT.SEARCH … NOCONTENT` + `DEL` — no keyspace scan at
|
|
@@ -1733,6 +1814,10 @@ var RedisVectorStore = class {
|
|
|
1733
1814
|
* from the index, so paging forward would skip their replacements. If a page comes back non-empty
|
|
1734
1815
|
* but `DEL` reports nothing removed (keys expired between query and delete, or the index is stale),
|
|
1735
1816
|
* the loop stops rather than spinning on results it can never consume.
|
|
1817
|
+
*
|
|
1818
|
+
* The documents aren't known until a page comes back, so each page locks the documents it touches, one
|
|
1819
|
+
* page at a time, and asks again with `INKEYS` once it holds them. A `replaceDocument` that finished in
|
|
1820
|
+
* between may have dropped those chunks, or rewritten them to no longer match.
|
|
1736
1821
|
*/
|
|
1737
1822
|
async removeWhere(filter) {
|
|
1738
1823
|
const keys = Object.keys(filter);
|
|
@@ -1754,6 +1839,7 @@ var RedisVectorStore = class {
|
|
|
1754
1839
|
}
|
|
1755
1840
|
const query = buildFilter(filter);
|
|
1756
1841
|
let removed = 0;
|
|
1842
|
+
let emptyPageBefore = false;
|
|
1757
1843
|
for (; ; ) {
|
|
1758
1844
|
const reply = await this.client.sendCommand([
|
|
1759
1845
|
"FT.SEARCH",
|
|
@@ -1770,14 +1856,46 @@ var RedisVectorStore = class {
|
|
|
1770
1856
|
if (page.length === 0) {
|
|
1771
1857
|
break;
|
|
1772
1858
|
}
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1859
|
+
const documents = page.map((key) => documentIdOf(stripPrefix(key, this.prefix)));
|
|
1860
|
+
const outcome = await this.withDocumentLocks(documents, async (assertHeld) => {
|
|
1861
|
+
const confirmed = parseSearchKeys(await this.client.sendCommand([
|
|
1862
|
+
"FT.SEARCH",
|
|
1863
|
+
this.index,
|
|
1864
|
+
query,
|
|
1865
|
+
"NOCONTENT",
|
|
1866
|
+
"INKEYS",
|
|
1867
|
+
String(page.length),
|
|
1868
|
+
...page,
|
|
1869
|
+
"LIMIT",
|
|
1870
|
+
"0",
|
|
1871
|
+
String(DELETE_BATCH),
|
|
1872
|
+
"DIALECT",
|
|
1873
|
+
"2"
|
|
1874
|
+
]));
|
|
1875
|
+
assertHeld();
|
|
1876
|
+
return confirmed.length === 0 ? {
|
|
1877
|
+
confirmed: 0,
|
|
1878
|
+
deleted: 0
|
|
1879
|
+
} : {
|
|
1880
|
+
confirmed: confirmed.length,
|
|
1881
|
+
deleted: Number(await this.client.sendCommand([
|
|
1882
|
+
"DEL",
|
|
1883
|
+
...confirmed
|
|
1884
|
+
]))
|
|
1885
|
+
};
|
|
1886
|
+
});
|
|
1887
|
+
if (outcome.confirmed === 0) {
|
|
1888
|
+
if (emptyPageBefore) {
|
|
1889
|
+
break;
|
|
1890
|
+
}
|
|
1891
|
+
emptyPageBefore = true;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
emptyPageBefore = false;
|
|
1895
|
+
if (!Number.isFinite(outcome.deleted) || outcome.deleted === 0) {
|
|
1778
1896
|
break;
|
|
1779
1897
|
}
|
|
1780
|
-
removed += deleted;
|
|
1898
|
+
removed += outcome.deleted;
|
|
1781
1899
|
}
|
|
1782
1900
|
return removed;
|
|
1783
1901
|
}
|
|
@@ -1875,6 +1993,10 @@ var RedisVectorStore = class {
|
|
|
1875
1993
|
return parseSearchReply(reply, this.prefix, "lexical");
|
|
1876
1994
|
}
|
|
1877
1995
|
};
|
|
1996
|
+
function lostLockError(documentId) {
|
|
1997
|
+
return new Error(`RedisVectorStore: lost the lock while changing document "${documentId}"; call again to repair it`);
|
|
1998
|
+
}
|
|
1999
|
+
__name(lostLockError, "lostLockError");
|
|
1878
2000
|
var SCAN_PAGE = 1e3;
|
|
1879
2001
|
var DELETE_BATCH = 512;
|
|
1880
2002
|
function stripPrefix(key, prefix) {
|
|
@@ -2941,9 +3063,10 @@ var RagIndex = class {
|
|
|
2941
3063
|
}
|
|
2942
3064
|
/**
|
|
2943
3065
|
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
2944
|
-
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres
|
|
2945
|
-
* adds the new chunks before dropping the old, so
|
|
2946
|
-
* `replaceDocument`, a crash mid-swap loses the
|
|
3066
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres and one
|
|
3067
|
+
* script on Redis, where a document too large for that adds the new chunks before dropping the old, so
|
|
3068
|
+
* a search can briefly see both. On a store without `replaceDocument`, a crash mid-swap loses the
|
|
3069
|
+
* document until the next `index`.
|
|
2947
3070
|
*/
|
|
2948
3071
|
async index(row) {
|
|
2949
3072
|
const { definition, deps } = this;
|