@dudousxd/nestjs-agent-rag 0.8.0 → 0.9.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.
package/dist/index.d.cts CHANGED
@@ -174,6 +174,32 @@ interface VectorStore {
174
174
  * `size` changed), or {@link VectorStore.remove remove} (orphaned).
175
175
  */
176
176
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
177
+ /**
178
+ * Read one document's chunks back — id, text, metadata — ordered by their position in the
179
+ * document, optionally paged with `limit`/`offset`.
180
+ *
181
+ * The gap this closes is that **the text a store holds is otherwise write-only**. Every other way
182
+ * back to it is a ranked query: {@link VectorStore.search} needs an embedding,
183
+ * {@link LexicalVectorStore.searchText} needs a query string, and both return whatever *matched*,
184
+ * in relevance order, capped at `topK`. Neither can answer "show me what was indexed for this
185
+ * document, in order" — the question you ask when retrieval returns the wrong passage and you need
186
+ * to know whether the chunker cut badly or the retriever chose badly. Without this, the only way to
187
+ * see stored text is to reach around the adapter into the backing engine, which is exactly the
188
+ * abstraction break a consumer adopts a store to avoid.
189
+ *
190
+ * Document-scoped rather than filter-scoped, matching {@link VectorStore.remove} and
191
+ * {@link VectorStore.updateMetadata}: ordering is the whole point, and "position within the
192
+ * document" is only meaningful once a document has been picked. Enumerate documents with
193
+ * {@link VectorStore.listDocuments} first when you need to sweep.
194
+ *
195
+ * An unknown `documentId` yields `[]`; it does not throw, matching `remove` and `updateMetadata`.
196
+ *
197
+ * NOT an access-control seam. It takes no metadata `filter` and applies none, so it cannot enforce
198
+ * one — a caller that gates retrieval on a metadata field (tenant, audience, collection) MUST
199
+ * resolve that gate itself before calling, exactly as it would before `remove`. It is deliberately
200
+ * shaped for an operator/debug surface, not for serving end users.
201
+ */
202
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
177
203
  /**
178
204
  * The distinct source document ids currently indexed, optionally narrowed by a metadata `filter` —
179
205
  * {@link VectorStore.listDocuments} without the metadata. Use it when you only need the id set (a
@@ -271,6 +297,25 @@ interface IndexedDocument {
271
297
  id: string;
272
298
  metadata?: Record<string, unknown>;
273
299
  }
300
+ /** One stored chunk, read back verbatim — what {@link VectorStore.listChunks} yields. */
301
+ interface StoredChunk {
302
+ /** The chunk id: `${documentId}#<n>`, or the bare document id for a single-chunk document. */
303
+ id: string;
304
+ /** Zero-based position within the document, parsed from the id. A bare id is chunk `0`. */
305
+ index: number;
306
+ /** The chunk text as it was indexed — the exact string the embedding was computed from. */
307
+ text: string;
308
+ metadata?: Record<string, unknown>;
309
+ }
310
+ /** Paging for {@link VectorStore.listChunks}. Omit both to read the whole document. */
311
+ interface ListChunksOptions {
312
+ /** Max chunks to return, applied after ordering by {@link StoredChunk.index}. */
313
+ limit?: number;
314
+ /** Chunks to skip, applied after ordering. */
315
+ offset?: number;
316
+ }
317
+ /** Chunk `n` of `${documentId}#<n>`; a bare document id (single-chunk document) is `0`. */
318
+ declare function chunkIndexOf(chunkId: string): number;
274
319
  /** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */
275
320
  declare function documentIdOf(chunkId: string): string;
276
321
 
@@ -296,6 +341,16 @@ declare class MemoryVectorStore implements VectorStore {
296
341
  */
297
342
  updateMetadata(documentId: string, patch: MetadataPatch): Promise<number>;
298
343
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
344
+ /**
345
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
346
+ *
347
+ * Insertion order is not document order — `upsert` stores whatever the caller passed, and a
348
+ * re-ingest that rewrites chunk 3 alone moves it to the end of the Map. So this sorts by the index
349
+ * parsed from the id, the same thing the SQL and Redis adapters order by, rather than trusting the
350
+ * Map. A reference adapter that returned "usually ordered" would let a consumer ship a bug that
351
+ * only shows up on the store tests are NOT written against.
352
+ */
353
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
299
354
  listDocumentIds(filter?: Record<string, unknown>): Promise<string[]>;
300
355
  removeMany(documentIds: string[]): Promise<void>;
301
356
  /**
@@ -374,6 +429,19 @@ declare class PgVectorStore implements VectorStore {
374
429
  */
375
430
  updateMetadata(documentId: string, patch: MetadataPatch): Promise<number>;
376
431
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
432
+ /**
433
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
434
+ *
435
+ * The order is computed from the id rather than stored: `#12` must sort after `#2`, which a plain
436
+ * `ORDER BY id` gets wrong on every document past ten chunks because it compares text. So the
437
+ * trailing number is extracted and cast to `int`, and a bare id (a single-chunk document, which
438
+ * carries no `#n` suffix) collapses to `0` — the same rule `chunkIndexOf` applies in the other two
439
+ * adapters.
440
+ *
441
+ * `embedding` is left out of the projection: it is the widest column in the table and useless to a
442
+ * caller reading text back.
443
+ */
444
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
377
445
  /** {@link listDocuments} without the metadata: one `DISTINCT` over the derived document id. */
378
446
  listDocumentIds(filter?: Record<string, unknown>): Promise<string[]>;
379
447
  /** N documents in one statement — the set-based form of {@link PgVectorStore.remove}. */
@@ -550,6 +618,25 @@ declare class RedisVectorStore implements LexicalVectorStore {
550
618
  * document owns. The glob is escaped so `gone` does not reach into `goner#0`.
551
619
  */
552
620
  private chunkKeys;
621
+ /**
622
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
623
+ *
624
+ * Reads the hashes directly (`HMGET text metadata_json`) rather than going through `FT.SEARCH`.
625
+ * Two reasons, and the second is the one that matters: the query language has no way to ask for
626
+ * "every chunk of this document" — `id` is the key name, not an indexed field — and `FT.SEARCH`
627
+ * caps at `LIMIT`, so paging a document through it would mean ordering by a field the index does
628
+ * not hold. `chunkKeys` is the same enumeration {@link RedisVectorStore.remove} and
629
+ * {@link RedisVectorStore.updateMetadata} already use, so "the chunks this returns" and "the chunks
630
+ * a delete would take" cannot drift apart.
631
+ *
632
+ * It inherits that method's cost too: one keyspace `SCAN` per call. Acceptable here because this
633
+ * is an inspection path (one document, on demand) and not something retrieval runs — but it is why
634
+ * this must not be put on a hot path or called in a loop over a corpus.
635
+ *
636
+ * `embedding` is deliberately not fetched: it is by far the largest field on the hash and nothing
637
+ * a caller reading text back can do with it.
638
+ */
639
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
553
640
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
554
641
  /**
555
642
  * The id-only half of {@link RedisVectorStore.listDocuments}, served by `FT.SEARCH … NOCONTENT`:
@@ -868,4 +955,4 @@ interface RetrievalTool {
868
955
  */
869
956
  declare function createRetrievalTool(retriever: Retriever, options?: RetrievalToolOptions): RetrievalTool;
870
957
 
871
- export { type ChunkOptions, type ChunkRecord, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, MemoryVectorStore, type MetadataPatch, type PgClient, PgVectorStore, type PgVectorStoreOptions, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalTool, type RetrievalToolOptions, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, applyMetadataPatch, chunkDocuments, chunkText, createRetrievalTool, documentIdOf, ingestChunks, ingestDocuments, isLexicalVectorStore };
958
+ export { type ChunkOptions, type ChunkRecord, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type PgClient, PgVectorStore, type PgVectorStoreOptions, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalTool, type RetrievalToolOptions, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, documentIdOf, ingestChunks, ingestDocuments, isLexicalVectorStore };
package/dist/index.d.ts CHANGED
@@ -174,6 +174,32 @@ interface VectorStore {
174
174
  * `size` changed), or {@link VectorStore.remove remove} (orphaned).
175
175
  */
176
176
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
177
+ /**
178
+ * Read one document's chunks back — id, text, metadata — ordered by their position in the
179
+ * document, optionally paged with `limit`/`offset`.
180
+ *
181
+ * The gap this closes is that **the text a store holds is otherwise write-only**. Every other way
182
+ * back to it is a ranked query: {@link VectorStore.search} needs an embedding,
183
+ * {@link LexicalVectorStore.searchText} needs a query string, and both return whatever *matched*,
184
+ * in relevance order, capped at `topK`. Neither can answer "show me what was indexed for this
185
+ * document, in order" — the question you ask when retrieval returns the wrong passage and you need
186
+ * to know whether the chunker cut badly or the retriever chose badly. Without this, the only way to
187
+ * see stored text is to reach around the adapter into the backing engine, which is exactly the
188
+ * abstraction break a consumer adopts a store to avoid.
189
+ *
190
+ * Document-scoped rather than filter-scoped, matching {@link VectorStore.remove} and
191
+ * {@link VectorStore.updateMetadata}: ordering is the whole point, and "position within the
192
+ * document" is only meaningful once a document has been picked. Enumerate documents with
193
+ * {@link VectorStore.listDocuments} first when you need to sweep.
194
+ *
195
+ * An unknown `documentId` yields `[]`; it does not throw, matching `remove` and `updateMetadata`.
196
+ *
197
+ * NOT an access-control seam. It takes no metadata `filter` and applies none, so it cannot enforce
198
+ * one — a caller that gates retrieval on a metadata field (tenant, audience, collection) MUST
199
+ * resolve that gate itself before calling, exactly as it would before `remove`. It is deliberately
200
+ * shaped for an operator/debug surface, not for serving end users.
201
+ */
202
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
177
203
  /**
178
204
  * The distinct source document ids currently indexed, optionally narrowed by a metadata `filter` —
179
205
  * {@link VectorStore.listDocuments} without the metadata. Use it when you only need the id set (a
@@ -271,6 +297,25 @@ interface IndexedDocument {
271
297
  id: string;
272
298
  metadata?: Record<string, unknown>;
273
299
  }
300
+ /** One stored chunk, read back verbatim — what {@link VectorStore.listChunks} yields. */
301
+ interface StoredChunk {
302
+ /** The chunk id: `${documentId}#<n>`, or the bare document id for a single-chunk document. */
303
+ id: string;
304
+ /** Zero-based position within the document, parsed from the id. A bare id is chunk `0`. */
305
+ index: number;
306
+ /** The chunk text as it was indexed — the exact string the embedding was computed from. */
307
+ text: string;
308
+ metadata?: Record<string, unknown>;
309
+ }
310
+ /** Paging for {@link VectorStore.listChunks}. Omit both to read the whole document. */
311
+ interface ListChunksOptions {
312
+ /** Max chunks to return, applied after ordering by {@link StoredChunk.index}. */
313
+ limit?: number;
314
+ /** Chunks to skip, applied after ordering. */
315
+ offset?: number;
316
+ }
317
+ /** Chunk `n` of `${documentId}#<n>`; a bare document id (single-chunk document) is `0`. */
318
+ declare function chunkIndexOf(chunkId: string): number;
274
319
  /** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */
275
320
  declare function documentIdOf(chunkId: string): string;
276
321
 
@@ -296,6 +341,16 @@ declare class MemoryVectorStore implements VectorStore {
296
341
  */
297
342
  updateMetadata(documentId: string, patch: MetadataPatch): Promise<number>;
298
343
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
344
+ /**
345
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
346
+ *
347
+ * Insertion order is not document order — `upsert` stores whatever the caller passed, and a
348
+ * re-ingest that rewrites chunk 3 alone moves it to the end of the Map. So this sorts by the index
349
+ * parsed from the id, the same thing the SQL and Redis adapters order by, rather than trusting the
350
+ * Map. A reference adapter that returned "usually ordered" would let a consumer ship a bug that
351
+ * only shows up on the store tests are NOT written against.
352
+ */
353
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
299
354
  listDocumentIds(filter?: Record<string, unknown>): Promise<string[]>;
300
355
  removeMany(documentIds: string[]): Promise<void>;
301
356
  /**
@@ -374,6 +429,19 @@ declare class PgVectorStore implements VectorStore {
374
429
  */
375
430
  updateMetadata(documentId: string, patch: MetadataPatch): Promise<number>;
376
431
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
432
+ /**
433
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
434
+ *
435
+ * The order is computed from the id rather than stored: `#12` must sort after `#2`, which a plain
436
+ * `ORDER BY id` gets wrong on every document past ten chunks because it compares text. So the
437
+ * trailing number is extracted and cast to `int`, and a bare id (a single-chunk document, which
438
+ * carries no `#n` suffix) collapses to `0` — the same rule `chunkIndexOf` applies in the other two
439
+ * adapters.
440
+ *
441
+ * `embedding` is left out of the projection: it is the widest column in the table and useless to a
442
+ * caller reading text back.
443
+ */
444
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
377
445
  /** {@link listDocuments} without the metadata: one `DISTINCT` over the derived document id. */
378
446
  listDocumentIds(filter?: Record<string, unknown>): Promise<string[]>;
379
447
  /** N documents in one statement — the set-based form of {@link PgVectorStore.remove}. */
@@ -550,6 +618,25 @@ declare class RedisVectorStore implements LexicalVectorStore {
550
618
  * document owns. The glob is escaped so `gone` does not reach into `goner#0`.
551
619
  */
552
620
  private chunkKeys;
621
+ /**
622
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
623
+ *
624
+ * Reads the hashes directly (`HMGET text metadata_json`) rather than going through `FT.SEARCH`.
625
+ * Two reasons, and the second is the one that matters: the query language has no way to ask for
626
+ * "every chunk of this document" — `id` is the key name, not an indexed field — and `FT.SEARCH`
627
+ * caps at `LIMIT`, so paging a document through it would mean ordering by a field the index does
628
+ * not hold. `chunkKeys` is the same enumeration {@link RedisVectorStore.remove} and
629
+ * {@link RedisVectorStore.updateMetadata} already use, so "the chunks this returns" and "the chunks
630
+ * a delete would take" cannot drift apart.
631
+ *
632
+ * It inherits that method's cost too: one keyspace `SCAN` per call. Acceptable here because this
633
+ * is an inspection path (one document, on demand) and not something retrieval runs — but it is why
634
+ * this must not be put on a hot path or called in a loop over a corpus.
635
+ *
636
+ * `embedding` is deliberately not fetched: it is by far the largest field on the hash and nothing
637
+ * a caller reading text back can do with it.
638
+ */
639
+ listChunks(documentId: string, options?: ListChunksOptions): Promise<StoredChunk[]>;
553
640
  listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
554
641
  /**
555
642
  * The id-only half of {@link RedisVectorStore.listDocuments}, served by `FT.SEARCH … NOCONTENT`:
@@ -868,4 +955,4 @@ interface RetrievalTool {
868
955
  */
869
956
  declare function createRetrievalTool(retriever: Retriever, options?: RetrievalToolOptions): RetrievalTool;
870
957
 
871
- export { type ChunkOptions, type ChunkRecord, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, MemoryVectorStore, type MetadataPatch, type PgClient, PgVectorStore, type PgVectorStoreOptions, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalTool, type RetrievalToolOptions, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, applyMetadataPatch, chunkDocuments, chunkText, createRetrievalTool, documentIdOf, ingestChunks, ingestDocuments, isLexicalVectorStore };
958
+ export { type ChunkOptions, type ChunkRecord, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type PgClient, PgVectorStore, type PgVectorStoreOptions, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalTool, type RetrievalToolOptions, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, documentIdOf, ingestChunks, ingestDocuments, isLexicalVectorStore };
package/dist/index.js CHANGED
@@ -91,6 +91,11 @@ var UnsafeRemovalError = class extends Error {
91
91
  this.name = "UnsafeRemovalError";
92
92
  }
93
93
  };
94
+ function chunkIndexOf(chunkId) {
95
+ const match = /#(\d+)$/.exec(chunkId);
96
+ return match?.[1] === void 0 ? 0 : Number(match[1]);
97
+ }
98
+ __name(chunkIndexOf, "chunkIndexOf");
94
99
  function documentIdOf(chunkId) {
95
100
  return chunkId.replace(/#\d+$/, "");
96
101
  }
@@ -215,6 +220,34 @@ var MemoryVectorStore = class {
215
220
  ...documents.values()
216
221
  ];
217
222
  }
223
+ /**
224
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
225
+ *
226
+ * Insertion order is not document order — `upsert` stores whatever the caller passed, and a
227
+ * re-ingest that rewrites chunk 3 alone moves it to the end of the Map. So this sorts by the index
228
+ * parsed from the id, the same thing the SQL and Redis adapters order by, rather than trusting the
229
+ * Map. A reference adapter that returned "usually ordered" would let a consumer ship a bug that
230
+ * only shows up on the store tests are NOT written against.
231
+ */
232
+ async listChunks(documentId, options) {
233
+ const chunks = [];
234
+ for (const record of this.records.values()) {
235
+ if (documentIdOf(record.id) !== documentId) {
236
+ continue;
237
+ }
238
+ chunks.push({
239
+ id: record.id,
240
+ index: chunkIndexOf(record.id),
241
+ text: record.text,
242
+ ...record.metadata !== void 0 ? {
243
+ metadata: record.metadata
244
+ } : {}
245
+ });
246
+ }
247
+ chunks.sort((a, b) => a.index - b.index);
248
+ const offset = options?.offset ?? 0;
249
+ return options?.limit === void 0 ? chunks.slice(offset) : chunks.slice(offset, offset + options.limit);
250
+ }
218
251
  async listDocumentIds(filter) {
219
252
  if (filterMatchesNothing(filter)) {
220
253
  return [];
@@ -401,6 +434,44 @@ var PgVectorStore = class {
401
434
  } : {}
402
435
  }));
403
436
  }
437
+ /**
438
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
439
+ *
440
+ * The order is computed from the id rather than stored: `#12` must sort after `#2`, which a plain
441
+ * `ORDER BY id` gets wrong on every document past ten chunks because it compares text. So the
442
+ * trailing number is extracted and cast to `int`, and a bare id (a single-chunk document, which
443
+ * carries no `#n` suffix) collapses to `0` — the same rule `chunkIndexOf` applies in the other two
444
+ * adapters.
445
+ *
446
+ * `embedding` is left out of the projection: it is the widest column in the table and useless to a
447
+ * caller reading text back.
448
+ */
449
+ async listChunks(documentId, options) {
450
+ const params = [
451
+ documentId
452
+ ];
453
+ let sql = `SELECT id, ${CHUNK_INDEX_FROM_ID} AS chunk_index, text, metadata
454
+ FROM ${this.table}
455
+ WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1
456
+ ORDER BY chunk_index`;
457
+ if (options?.limit !== void 0) {
458
+ params.push(options.limit);
459
+ sql += ` LIMIT $${params.length}`;
460
+ }
461
+ if (options?.offset !== void 0) {
462
+ params.push(options.offset);
463
+ sql += ` OFFSET $${params.length}`;
464
+ }
465
+ const rows = await this.client.query(sql, params);
466
+ return rows.map((row) => ({
467
+ id: row.id,
468
+ index: Number(row.chunk_index),
469
+ text: row.text,
470
+ ...row.metadata !== null ? {
471
+ metadata: row.metadata
472
+ } : {}
473
+ }));
474
+ }
404
475
  /** {@link listDocuments} without the metadata: one `DISTINCT` over the derived document id. */
405
476
  async listDocumentIds(filter) {
406
477
  if (filterMatchesNothing(filter)) {
@@ -511,6 +582,7 @@ function buildWhere(filter, startIndex) {
511
582
  }
512
583
  __name(buildWhere, "buildWhere");
513
584
  var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
585
+ var CHUNK_INDEX_FROM_ID = "COALESCE((substring(id from '#([0-9]+)$'))::int, 0)";
514
586
  function toVectorLiteral(embedding) {
515
587
  return `[${embedding.join(",")}]`;
516
588
  }
@@ -803,6 +875,58 @@ var RedisVectorStore = class {
803
875
  ...keys
804
876
  ];
805
877
  }
878
+ /**
879
+ * The chunks of one document, in document order. See {@link VectorStore.listChunks}.
880
+ *
881
+ * Reads the hashes directly (`HMGET text metadata_json`) rather than going through `FT.SEARCH`.
882
+ * Two reasons, and the second is the one that matters: the query language has no way to ask for
883
+ * "every chunk of this document" — `id` is the key name, not an indexed field — and `FT.SEARCH`
884
+ * caps at `LIMIT`, so paging a document through it would mean ordering by a field the index does
885
+ * not hold. `chunkKeys` is the same enumeration {@link RedisVectorStore.remove} and
886
+ * {@link RedisVectorStore.updateMetadata} already use, so "the chunks this returns" and "the chunks
887
+ * a delete would take" cannot drift apart.
888
+ *
889
+ * It inherits that method's cost too: one keyspace `SCAN` per call. Acceptable here because this
890
+ * is an inspection path (one document, on demand) and not something retrieval runs — but it is why
891
+ * this must not be put on a hot path or called in a loop over a corpus.
892
+ *
893
+ * `embedding` is deliberately not fetched: it is by far the largest field on the hash and nothing
894
+ * a caller reading text back can do with it.
895
+ */
896
+ async listChunks(documentId, options) {
897
+ const keys = await this.chunkKeys(documentId);
898
+ const ordered = keys.map((key) => ({
899
+ key,
900
+ id: key.slice(this.prefix.length)
901
+ })).map((entry) => ({
902
+ ...entry,
903
+ index: chunkIndexOf(entry.id)
904
+ })).sort((a, b) => a.index - b.index);
905
+ const offset = options?.offset ?? 0;
906
+ const page = options?.limit === void 0 ? ordered.slice(offset) : ordered.slice(offset, offset + options.limit);
907
+ const chunks = [];
908
+ for (const entry of page) {
909
+ const reply = await this.client.sendCommand([
910
+ "HMGET",
911
+ entry.key,
912
+ "text",
913
+ "metadata_json"
914
+ ]);
915
+ const fields = Array.isArray(reply) ? reply : [];
916
+ const text = fields[0] === null || fields[0] === void 0 ? void 0 : toStr(fields[0]);
917
+ if (text === void 0) {
918
+ continue;
919
+ }
920
+ const raw = fields[1] === null || fields[1] === void 0 ? void 0 : toStr(fields[1]);
921
+ chunks.push({
922
+ id: entry.id,
923
+ index: entry.index,
924
+ text,
925
+ ...parseMetadata(raw)
926
+ });
927
+ }
928
+ return chunks;
929
+ }
806
930
  async listDocuments(filter) {
807
931
  if (filterMatchesNothing(filter)) {
808
932
  return [];
@@ -1692,6 +1816,7 @@ export {
1692
1816
  UnsafeRemovalError,
1693
1817
  applyMetadataPatch,
1694
1818
  chunkDocuments,
1819
+ chunkIndexOf,
1695
1820
  chunkText,
1696
1821
  createRetrievalTool,
1697
1822
  documentIdOf,