@dudousxd/nestjs-agent-rag 0.14.0 → 0.14.2
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 +2 -1
- package/dist/index.cjs +135 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -14
- package/dist/index.d.ts +29 -14
- package/dist/index.js +135 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -279,14 +279,15 @@ interface LexicalVectorStore extends VectorStore {
|
|
|
279
279
|
searchText(query: string, options: VectorSearchOptions): Promise<Passage[]>;
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
|
-
* An **optional** capability: swap all of a document's chunks
|
|
283
|
-
*
|
|
284
|
-
*
|
|
282
|
+
* An **optional** capability: swap all of a document's chunks safely under concurrent calls, so two
|
|
283
|
+
* writers can't leave a mix of versions behind. Postgres also makes the swap atomic for searches and
|
|
284
|
+
* crashes; Redis can't, and only guarantees the end state. {@link replaceDocumentChunks} falls back to
|
|
285
|
+
* `remove` plus `upsert` for the others.
|
|
285
286
|
*/
|
|
286
287
|
interface AtomicReplaceVectorStore extends VectorStore {
|
|
287
288
|
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
288
289
|
}
|
|
289
|
-
/** Replaces a document's chunks
|
|
290
|
+
/** Replaces a document's chunks through the store's safe swap, where it has one. */
|
|
290
291
|
declare function replaceDocumentChunks(store: VectorStore, documentId: string, records: VectorRecord[]): Promise<void>;
|
|
291
292
|
/**
|
|
292
293
|
* Does this store carry the optional {@link LexicalVectorStore} capability? Lets a consumer pick the
|
|
@@ -406,8 +407,9 @@ declare function describeSharedSource(legs: readonly unknown[]): Omit<RetrievalD
|
|
|
406
407
|
* (`removeWhere` refusing an empty filter, an empty-array filter deleting nothing) are behavioural,
|
|
407
408
|
* not incidental to Redis.
|
|
408
409
|
*/
|
|
409
|
-
declare class MemoryVectorStore implements
|
|
410
|
+
declare class MemoryVectorStore implements AtomicReplaceVectorStore {
|
|
410
411
|
private readonly records;
|
|
412
|
+
private readonly replacing;
|
|
411
413
|
/**
|
|
412
414
|
* Telemetry self-description. No `collection`: there is no namespace to name — the corpus IS this
|
|
413
415
|
* instance, and two `MemoryVectorStore`s in one process are two corpora with nothing to tell them
|
|
@@ -415,6 +417,8 @@ declare class MemoryVectorStore implements VectorStore {
|
|
|
415
417
|
*/
|
|
416
418
|
describeRetrieval(): RetrievalDescriptor;
|
|
417
419
|
upsert(records: VectorRecord[]): Promise<void>;
|
|
420
|
+
/** Replaces take turns per document. A `remove` during one isn't queued, and may be undone by it. */
|
|
421
|
+
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
418
422
|
remove(documentId: string): Promise<void>;
|
|
419
423
|
/**
|
|
420
424
|
* Merge `patch` into every chunk of `documentId`, leaving `text` and `embedding` untouched. The
|
|
@@ -671,6 +675,10 @@ declare class PgVectorStore implements AtomicReplaceVectorStore {
|
|
|
671
675
|
/**
|
|
672
676
|
* Swaps a document's chunks in one transaction, so a crash can't lose it and a search sees the old
|
|
673
677
|
* version or the new one. A client without `transaction` falls back to two statements.
|
|
678
|
+
*
|
|
679
|
+
* Concurrent swaps of one document take turns on an advisory lock. Without it the second `DELETE`
|
|
680
|
+
* can't see the rows the first just committed, so the first's tail survives next to the second's
|
|
681
|
+
* chunks, and overlapping writes can deadlock.
|
|
674
682
|
*/
|
|
675
683
|
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
676
684
|
private upsertWith;
|
|
@@ -770,12 +778,6 @@ declare class PgLexicalVectorStore extends PgVectorStore implements LexicalVecto
|
|
|
770
778
|
private tsvector;
|
|
771
779
|
}
|
|
772
780
|
|
|
773
|
-
/**
|
|
774
|
-
* The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.
|
|
775
|
-
* Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so
|
|
776
|
-
* this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the
|
|
777
|
-
* RediSearch module (Redis Stack / Redis 8+).
|
|
778
|
-
*/
|
|
779
781
|
interface RedisSearchClient {
|
|
780
782
|
sendCommand(args: (string | Buffer)[]): Promise<unknown>;
|
|
781
783
|
}
|
|
@@ -837,7 +839,7 @@ interface RedisVectorStoreOptions {
|
|
|
837
839
|
* {@link LexicalVectorStore}: {@link RedisVectorStore.searchText} is BM25 over that same index —
|
|
838
840
|
* the lexical half of hybrid search with no second index to build, feed, or invalidate.
|
|
839
841
|
*/
|
|
840
|
-
declare class RedisVectorStore implements LexicalVectorStore {
|
|
842
|
+
declare class RedisVectorStore implements LexicalVectorStore, AtomicReplaceVectorStore {
|
|
841
843
|
private readonly client;
|
|
842
844
|
private readonly index;
|
|
843
845
|
private readonly prefix;
|
|
@@ -879,6 +881,18 @@ declare class RedisVectorStore implements LexicalVectorStore {
|
|
|
879
881
|
private reconcileSchema;
|
|
880
882
|
private createIndex;
|
|
881
883
|
upsert(records: VectorRecord[]): Promise<void>;
|
|
884
|
+
/**
|
|
885
|
+
* Replaces a document's chunks, one writer at a time per document. Redis can't swap them in one step
|
|
886
|
+
* without scanning the keyspace inside a script, so a search may briefly see old and new chunks
|
|
887
|
+
* together, but concurrent calls can't leave them mixed. The new chunks go in before the old ones
|
|
888
|
+
* come out, so a crash leaves the document searchable and the next call repairs it.
|
|
889
|
+
*/
|
|
890
|
+
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
891
|
+
/**
|
|
892
|
+
* A lease, so a crashed writer can't block the document forever. It is renewed while the work runs,
|
|
893
|
+
* which keeps a large document from outliving it.
|
|
894
|
+
*/
|
|
895
|
+
private withDocumentLock;
|
|
882
896
|
/**
|
|
883
897
|
* Single-document deletion, by `SCAN … MATCH`.
|
|
884
898
|
*
|
|
@@ -1700,8 +1714,9 @@ declare class RagIndex<Row, Scope extends RagScopeDefinition<Row>> {
|
|
|
1700
1714
|
search(query: string, options: RagSearchOptions<Scope>): Promise<Passage[]>;
|
|
1701
1715
|
/**
|
|
1702
1716
|
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
1703
|
-
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres
|
|
1704
|
-
*
|
|
1717
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres. Redis
|
|
1718
|
+
* adds the new chunks before dropping the old, so a search can briefly see both. On a store without
|
|
1719
|
+
* `replaceDocument`, a crash mid-swap loses the document until the next `index`.
|
|
1705
1720
|
*/
|
|
1706
1721
|
index(row: Row): Promise<number>;
|
|
1707
1722
|
/** Takes the id itself once the row is gone. */
|
package/dist/index.d.ts
CHANGED
|
@@ -279,14 +279,15 @@ interface LexicalVectorStore extends VectorStore {
|
|
|
279
279
|
searchText(query: string, options: VectorSearchOptions): Promise<Passage[]>;
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
|
-
* An **optional** capability: swap all of a document's chunks
|
|
283
|
-
*
|
|
284
|
-
*
|
|
282
|
+
* An **optional** capability: swap all of a document's chunks safely under concurrent calls, so two
|
|
283
|
+
* writers can't leave a mix of versions behind. Postgres also makes the swap atomic for searches and
|
|
284
|
+
* crashes; Redis can't, and only guarantees the end state. {@link replaceDocumentChunks} falls back to
|
|
285
|
+
* `remove` plus `upsert` for the others.
|
|
285
286
|
*/
|
|
286
287
|
interface AtomicReplaceVectorStore extends VectorStore {
|
|
287
288
|
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
288
289
|
}
|
|
289
|
-
/** Replaces a document's chunks
|
|
290
|
+
/** Replaces a document's chunks through the store's safe swap, where it has one. */
|
|
290
291
|
declare function replaceDocumentChunks(store: VectorStore, documentId: string, records: VectorRecord[]): Promise<void>;
|
|
291
292
|
/**
|
|
292
293
|
* Does this store carry the optional {@link LexicalVectorStore} capability? Lets a consumer pick the
|
|
@@ -406,8 +407,9 @@ declare function describeSharedSource(legs: readonly unknown[]): Omit<RetrievalD
|
|
|
406
407
|
* (`removeWhere` refusing an empty filter, an empty-array filter deleting nothing) are behavioural,
|
|
407
408
|
* not incidental to Redis.
|
|
408
409
|
*/
|
|
409
|
-
declare class MemoryVectorStore implements
|
|
410
|
+
declare class MemoryVectorStore implements AtomicReplaceVectorStore {
|
|
410
411
|
private readonly records;
|
|
412
|
+
private readonly replacing;
|
|
411
413
|
/**
|
|
412
414
|
* Telemetry self-description. No `collection`: there is no namespace to name — the corpus IS this
|
|
413
415
|
* instance, and two `MemoryVectorStore`s in one process are two corpora with nothing to tell them
|
|
@@ -415,6 +417,8 @@ declare class MemoryVectorStore implements VectorStore {
|
|
|
415
417
|
*/
|
|
416
418
|
describeRetrieval(): RetrievalDescriptor;
|
|
417
419
|
upsert(records: VectorRecord[]): Promise<void>;
|
|
420
|
+
/** Replaces take turns per document. A `remove` during one isn't queued, and may be undone by it. */
|
|
421
|
+
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
418
422
|
remove(documentId: string): Promise<void>;
|
|
419
423
|
/**
|
|
420
424
|
* Merge `patch` into every chunk of `documentId`, leaving `text` and `embedding` untouched. The
|
|
@@ -671,6 +675,10 @@ declare class PgVectorStore implements AtomicReplaceVectorStore {
|
|
|
671
675
|
/**
|
|
672
676
|
* Swaps a document's chunks in one transaction, so a crash can't lose it and a search sees the old
|
|
673
677
|
* version or the new one. A client without `transaction` falls back to two statements.
|
|
678
|
+
*
|
|
679
|
+
* Concurrent swaps of one document take turns on an advisory lock. Without it the second `DELETE`
|
|
680
|
+
* can't see the rows the first just committed, so the first's tail survives next to the second's
|
|
681
|
+
* chunks, and overlapping writes can deadlock.
|
|
674
682
|
*/
|
|
675
683
|
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
676
684
|
private upsertWith;
|
|
@@ -770,12 +778,6 @@ declare class PgLexicalVectorStore extends PgVectorStore implements LexicalVecto
|
|
|
770
778
|
private tsvector;
|
|
771
779
|
}
|
|
772
780
|
|
|
773
|
-
/**
|
|
774
|
-
* The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.
|
|
775
|
-
* Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so
|
|
776
|
-
* this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the
|
|
777
|
-
* RediSearch module (Redis Stack / Redis 8+).
|
|
778
|
-
*/
|
|
779
781
|
interface RedisSearchClient {
|
|
780
782
|
sendCommand(args: (string | Buffer)[]): Promise<unknown>;
|
|
781
783
|
}
|
|
@@ -837,7 +839,7 @@ interface RedisVectorStoreOptions {
|
|
|
837
839
|
* {@link LexicalVectorStore}: {@link RedisVectorStore.searchText} is BM25 over that same index —
|
|
838
840
|
* the lexical half of hybrid search with no second index to build, feed, or invalidate.
|
|
839
841
|
*/
|
|
840
|
-
declare class RedisVectorStore implements LexicalVectorStore {
|
|
842
|
+
declare class RedisVectorStore implements LexicalVectorStore, AtomicReplaceVectorStore {
|
|
841
843
|
private readonly client;
|
|
842
844
|
private readonly index;
|
|
843
845
|
private readonly prefix;
|
|
@@ -879,6 +881,18 @@ declare class RedisVectorStore implements LexicalVectorStore {
|
|
|
879
881
|
private reconcileSchema;
|
|
880
882
|
private createIndex;
|
|
881
883
|
upsert(records: VectorRecord[]): Promise<void>;
|
|
884
|
+
/**
|
|
885
|
+
* Replaces a document's chunks, one writer at a time per document. Redis can't swap them in one step
|
|
886
|
+
* without scanning the keyspace inside a script, so a search may briefly see old and new chunks
|
|
887
|
+
* together, but concurrent calls can't leave them mixed. The new chunks go in before the old ones
|
|
888
|
+
* come out, so a crash leaves the document searchable and the next call repairs it.
|
|
889
|
+
*/
|
|
890
|
+
replaceDocument(documentId: string, records: VectorRecord[]): Promise<void>;
|
|
891
|
+
/**
|
|
892
|
+
* A lease, so a crashed writer can't block the document forever. It is renewed while the work runs,
|
|
893
|
+
* which keeps a large document from outliving it.
|
|
894
|
+
*/
|
|
895
|
+
private withDocumentLock;
|
|
882
896
|
/**
|
|
883
897
|
* Single-document deletion, by `SCAN … MATCH`.
|
|
884
898
|
*
|
|
@@ -1700,8 +1714,9 @@ declare class RagIndex<Row, Scope extends RagScopeDefinition<Row>> {
|
|
|
1700
1714
|
search(query: string, options: RagSearchOptions<Scope>): Promise<Passage[]>;
|
|
1701
1715
|
/**
|
|
1702
1716
|
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
1703
|
-
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres
|
|
1704
|
-
*
|
|
1717
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres. Redis
|
|
1718
|
+
* adds the new chunks before dropping the old, so a search can briefly see both. On a store without
|
|
1719
|
+
* `replaceDocument`, a crash mid-swap loses the document until the next `index`.
|
|
1705
1720
|
*/
|
|
1706
1721
|
index(row: Row): Promise<number>;
|
|
1707
1722
|
/** Takes the id itself once the row is gone. */
|
package/dist/index.js
CHANGED
|
@@ -191,6 +191,7 @@ var MemoryVectorStore = class {
|
|
|
191
191
|
__name(this, "MemoryVectorStore");
|
|
192
192
|
}
|
|
193
193
|
records = /* @__PURE__ */ new Map();
|
|
194
|
+
replacing = /* @__PURE__ */ new Map();
|
|
194
195
|
/**
|
|
195
196
|
* Telemetry self-description. No `collection`: there is no namespace to name — the corpus IS this
|
|
196
197
|
* instance, and two `MemoryVectorStore`s in one process are two corpora with nothing to tell them
|
|
@@ -206,6 +207,23 @@ var MemoryVectorStore = class {
|
|
|
206
207
|
this.records.set(record.id, record);
|
|
207
208
|
}
|
|
208
209
|
}
|
|
210
|
+
/** Replaces take turns per document. A `remove` during one isn't queued, and may be undone by it. */
|
|
211
|
+
async replaceDocument(documentId, records) {
|
|
212
|
+
const previous = this.replacing.get(documentId) ?? Promise.resolve();
|
|
213
|
+
const run = previous.catch(() => {
|
|
214
|
+
}).then(async () => {
|
|
215
|
+
await this.remove(documentId);
|
|
216
|
+
await this.upsert(records);
|
|
217
|
+
});
|
|
218
|
+
this.replacing.set(documentId, run);
|
|
219
|
+
try {
|
|
220
|
+
await run;
|
|
221
|
+
} finally {
|
|
222
|
+
if (this.replacing.get(documentId) === run) {
|
|
223
|
+
this.replacing.delete(documentId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
209
227
|
async remove(documentId) {
|
|
210
228
|
for (const id of this.records.keys()) {
|
|
211
229
|
if (documentIdOf(id) === documentId) {
|
|
@@ -679,6 +697,10 @@ var PgVectorStore = class {
|
|
|
679
697
|
/**
|
|
680
698
|
* Swaps a document's chunks in one transaction, so a crash can't lose it and a search sees the old
|
|
681
699
|
* version or the new one. A client without `transaction` falls back to two statements.
|
|
700
|
+
*
|
|
701
|
+
* Concurrent swaps of one document take turns on an advisory lock. Without it the second `DELETE`
|
|
702
|
+
* can't see the rows the first just committed, so the first's tail survives next to the second's
|
|
703
|
+
* chunks, and overlapping writes can deadlock.
|
|
682
704
|
*/
|
|
683
705
|
async replaceDocument(documentId, records) {
|
|
684
706
|
const swap = /* @__PURE__ */ __name(async (client) => {
|
|
@@ -688,7 +710,16 @@ var PgVectorStore = class {
|
|
|
688
710
|
await this.upsertWith(client, records);
|
|
689
711
|
}, "swap");
|
|
690
712
|
const transaction = this.client.transaction?.bind(this.client);
|
|
691
|
-
|
|
713
|
+
if (transaction === void 0) {
|
|
714
|
+
await swap(this.client);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
await transaction(async (client) => {
|
|
718
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [
|
|
719
|
+
`${this.table}:${stripNulBytes(documentId)}`
|
|
720
|
+
]);
|
|
721
|
+
await swap(client);
|
|
722
|
+
});
|
|
692
723
|
}
|
|
693
724
|
async upsertWith(client, records) {
|
|
694
725
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -1103,6 +1134,11 @@ function toVectorLiteral(embedding) {
|
|
|
1103
1134
|
__name(toVectorLiteral, "toVectorLiteral");
|
|
1104
1135
|
|
|
1105
1136
|
// src/redis-vector-store.ts
|
|
1137
|
+
import { randomUUID } from "crypto";
|
|
1138
|
+
var REPLACE_LOCK_TTL_MS = 3e4;
|
|
1139
|
+
var REPLACE_LOCK_WAIT_MS = 12e4;
|
|
1140
|
+
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
|
+
var RELEASE_LOCK_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0";
|
|
1106
1142
|
var RedisVectorSchemaMismatchError = class extends Error {
|
|
1107
1143
|
static {
|
|
1108
1144
|
__name(this, "RedisVectorSchemaMismatchError");
|
|
@@ -1264,6 +1300,92 @@ var RedisVectorStore = class {
|
|
|
1264
1300
|
}
|
|
1265
1301
|
}
|
|
1266
1302
|
/**
|
|
1303
|
+
* Replaces a document's chunks, one writer at a time per document. Redis can't swap them in one step
|
|
1304
|
+
* without scanning the keyspace inside a script, so a search may briefly see old and new chunks
|
|
1305
|
+
* together, but concurrent calls can't leave them mixed. The new chunks go in before the old ones
|
|
1306
|
+
* come out, so a crash leaves the document searchable and the next call repairs it.
|
|
1307
|
+
*/
|
|
1308
|
+
async replaceDocument(documentId, records) {
|
|
1309
|
+
await this.withDocumentLock(documentId, async (assertHeld) => {
|
|
1310
|
+
await this.upsert(records);
|
|
1311
|
+
assertHeld();
|
|
1312
|
+
const current = new Set(records.map((record) => `${this.prefix}${record.id}`));
|
|
1313
|
+
const stale = (await this.chunkKeys(documentId)).filter((key) => !current.has(key));
|
|
1314
|
+
assertHeld();
|
|
1315
|
+
if (stale.length > 0) {
|
|
1316
|
+
await this.client.sendCommand([
|
|
1317
|
+
"DEL",
|
|
1318
|
+
...stale
|
|
1319
|
+
]);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* A lease, so a crashed writer can't block the document forever. It is renewed while the work runs,
|
|
1325
|
+
* which keeps a large document from outliving it.
|
|
1326
|
+
*/
|
|
1327
|
+
async withDocumentLock(documentId, work) {
|
|
1328
|
+
const key = `${this.index}:replace-lock:${documentId}`;
|
|
1329
|
+
const token = randomUUID();
|
|
1330
|
+
const deadline = Date.now() + REPLACE_LOCK_WAIT_MS;
|
|
1331
|
+
let delay = 10;
|
|
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;
|
|
1347
|
+
const assertHeld = /* @__PURE__ */ __name(() => {
|
|
1348
|
+
if (lost) {
|
|
1349
|
+
throw new Error(`RedisVectorStore: lost the lock while changing document "${documentId}"; call again to repair it`);
|
|
1350
|
+
}
|
|
1351
|
+
}, "assertHeld");
|
|
1352
|
+
const renewal = setInterval(() => {
|
|
1353
|
+
void Promise.resolve(this.client.sendCommand([
|
|
1354
|
+
"EVAL",
|
|
1355
|
+
RENEW_LOCK_SCRIPT,
|
|
1356
|
+
"1",
|
|
1357
|
+
key,
|
|
1358
|
+
token,
|
|
1359
|
+
String(REPLACE_LOCK_TTL_MS)
|
|
1360
|
+
])).then(
|
|
1361
|
+
(renewed) => {
|
|
1362
|
+
if (Number(renewed) === 0) {
|
|
1363
|
+
lost = true;
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
// A failed renewal isn't a lost lock yet: the lease may still have time left.
|
|
1367
|
+
() => {
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
}, REPLACE_LOCK_TTL_MS / 3);
|
|
1371
|
+
renewal.unref?.();
|
|
1372
|
+
try {
|
|
1373
|
+
const result = await work(assertHeld);
|
|
1374
|
+
assertHeld();
|
|
1375
|
+
return result;
|
|
1376
|
+
} finally {
|
|
1377
|
+
clearInterval(renewal);
|
|
1378
|
+
await Promise.resolve(this.client.sendCommand([
|
|
1379
|
+
"EVAL",
|
|
1380
|
+
RELEASE_LOCK_SCRIPT,
|
|
1381
|
+
"1",
|
|
1382
|
+
key,
|
|
1383
|
+
token
|
|
1384
|
+
])).catch(() => {
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1267
1389
|
* Single-document deletion, by `SCAN … MATCH`.
|
|
1268
1390
|
*
|
|
1269
1391
|
* This deliberately stays a keyspace scan even though the rest of this class no longer needs one.
|
|
@@ -1284,13 +1406,15 @@ var RedisVectorStore = class {
|
|
|
1284
1406
|
* than N scans). Neither depends on a field that older chunks lack, so neither has a migration.
|
|
1285
1407
|
*/
|
|
1286
1408
|
async remove(documentId) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1409
|
+
await this.withDocumentLock(documentId, async () => {
|
|
1410
|
+
const keys = await this.chunkKeys(documentId);
|
|
1411
|
+
if (keys.length > 0) {
|
|
1412
|
+
await this.client.sendCommand([
|
|
1413
|
+
"DEL",
|
|
1414
|
+
...keys
|
|
1415
|
+
]);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1294
1418
|
}
|
|
1295
1419
|
/**
|
|
1296
1420
|
* Rewrite the metadata of every chunk of `documentId` in place, leaving `text` and `embedding`
|
|
@@ -2817,8 +2941,9 @@ var RagIndex = class {
|
|
|
2817
2941
|
}
|
|
2818
2942
|
/**
|
|
2819
2943
|
* Replaces the row's chunks and returns how many were written. Embeddings come first: if the
|
|
2820
|
-
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres
|
|
2821
|
-
*
|
|
2944
|
+
* provider fails, the previous chunks are still there. The swap is one transaction on Postgres. Redis
|
|
2945
|
+
* adds the new chunks before dropping the old, so a search can briefly see both. On a store without
|
|
2946
|
+
* `replaceDocument`, a crash mid-swap loses the document until the next `index`.
|
|
2822
2947
|
*/
|
|
2823
2948
|
async index(row) {
|
|
2824
2949
|
const { definition, deps } = this;
|