@h-ai/vecdb 1.0.0-beta.25 → 1.0.0-beta.27

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.en.md CHANGED
@@ -79,6 +79,19 @@ Insert/upsert take collection/documents, delete IDs, yielding void; search takes
79
79
  | `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
80
80
  | `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
81
81
  | `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
82
+ | `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
83
+
84
+ Use `scan` to page through document IDs for reconciliation or cleanup. `collection` is the collection name. `options.limit` defaults to 64 and accepts 1 through 128. Omit `options.cursor` for the first page. Each page contains `{ id }` items. If `nextCursor` is present, continue with that cursor on the same collection and backend even when `items` is empty. Concurrent writes or deletions can affect pagination, so reconciliation jobs should run repeated sweeps. Qdrant returns only points that map back to framework document IDs.
85
+
86
+ ```ts
87
+ // Input: collection name and page size. Output: HaiResult<VectorScanPage>.
88
+ const page = await vecdb.vector.scan('documents', { limit: 32 })
89
+ // On success, pass the cursor and page size to the same collection when another page exists.
90
+ if (page.success && page.data.nextCursor !== undefined) {
91
+ const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
92
+ // On success, nextPage.data.items contains the next IDs. An absent nextCursor marks the last page.
93
+ }
94
+ ```
82
95
 
83
96
  ### Production shared state
84
97
 
package/README.ja.md CHANGED
@@ -79,6 +79,19 @@ insert/upsert は集合と文書配列、delete は ID 配列から void。searc
79
79
  | `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
80
80
  | `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
81
81
  | `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
82
+ | `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
83
+
84
+ `scan` は照合やクリーンアップのために文書 ID をページ単位で取得します。`collection` はコレクション名です。`options.limit` の既定値は 64、範囲は 1〜128 です。最初のページでは `options.cursor` を省略します。各ページの `items` には `{ id }` が入ります。`items` が空でも `nextCursor` があれば、同じバックエンドとコレクションにそのカーソルを渡して続けます。同時書き込みや削除でページの内容が変わるため、照合処理はスキャンを繰り返します。Qdrant はフレームワークの文書 ID に対応するポイントだけを返します。
85
+
86
+ ```ts
87
+ // 入力はコレクション名とページサイズ、出力は HaiResult<VectorScanPage>。
88
+ const page = await vecdb.vector.scan('documents', { limit: 32 })
89
+ // 成功して次のページがある場合は、同じコレクションにカーソルとページサイズを渡す。
90
+ if (page.success && page.data.nextCursor !== undefined) {
91
+ const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
92
+ // 成功時の nextPage.data.items は次ページの ID。nextCursor のないページが最後。
93
+ }
94
+ ```
82
95
 
83
96
  ### 本番の共有状態
84
97
 
package/README.md CHANGED
@@ -79,6 +79,19 @@ if (!closeResult.success) {
79
79
  | `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
80
80
  | `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
81
81
  | `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
82
+ | `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
83
+
84
+ `scan` 分页返回文档 ID,可用于核对或批量清理集合。`collection` 是集合名,`options.limit` 默认为 64,取值为 1 到 128,`options.cursor` 在首页省略。每页的 `items` 只包含 `{ id }`。有 `nextCursor` 时,即使本页为空也继续读取,并将游标传给同一后端的同一集合。并发写入或删除可能改变分页结果,清理任务应重复扫描。Qdrant 只返回能映射回框架文档的记录。
85
+
86
+ ```ts
87
+ // 输入集合名与页大小,输出 HaiResult<VectorScanPage>。
88
+ const page = await vecdb.vector.scan('documents', { limit: 32 })
89
+ // 成功且存在下一页时,将上一页游标与页大小传入同一集合。
90
+ if (page.success && page.data.nextCursor !== undefined) {
91
+ const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
92
+ // nextPage 成功时 data.items 是下一页文档 ID,data.nextCursor 缺失时已到末页。
93
+ }
94
+ ```
82
95
 
83
96
  ### 生产共享状态
84
97
 
package/dist/index.d.ts CHANGED
@@ -631,6 +631,45 @@ interface VectorOperations {
631
631
  * Qdrant 使用精确 count,不依赖集合信息的近似统计。
632
632
  */
633
633
  count: (collection: string) => Promise<HaiResult<number>>;
634
+ /**
635
+ * 分页读取文档 ID,仅返回可以传给本模块删除操作的 ID。
636
+ *
637
+ * @param collection - 集合名称
638
+ * @param options - 扫描游标与每页条数
639
+ * @returns 本页 ID 和后续游标。扫描不提供跨页快照。
640
+ * @example await vecdb.vector.scan('docs', { limit: 32 })
641
+ */
642
+ scan?: (collection: string, options?: VectorScanOptions) => Promise<HaiResult<VectorScanPage>>;
643
+ }
644
+ /**
645
+ * 文档 ID 扫描选项。
646
+ * @example
647
+ * ```ts
648
+ * import type { VectorScanOptions } from '@h-ai/vecdb'
649
+ * const options: VectorScanOptions = { limit: 32 }
650
+ * ```
651
+ */
652
+ interface VectorScanOptions {
653
+ /** 上一页返回的游标,首次扫描省略。游标仅用于同一后端的同一个集合。 */
654
+ cursor?: string;
655
+ /** 每页最多返回的文档数量,默认 64,允许 1 到 128。 */
656
+ limit?: number;
657
+ }
658
+ /**
659
+ * 一页文档 ID。
660
+ * @example
661
+ * ```ts
662
+ * import type { VectorScanPage } from '@h-ai/vecdb'
663
+ * const page: VectorScanPage = { items: [{ id: 'doc-1' }] }
664
+ * ```
665
+ */
666
+ interface VectorScanPage {
667
+ /** 文档 ID。Qdrant 只包含可映射回框架文档的记录。 */
668
+ items: Array<{
669
+ id: string;
670
+ }>;
671
+ /** 下一页游标。即使当前页为空,只要存在游标就应继续扫描。 */
672
+ nextCursor?: string;
634
673
  }
635
674
  /**
636
675
  * 向量数据库服务接口(通过 `vecdb` 对象访问)
@@ -706,6 +745,27 @@ interface VecdbConnection {
706
745
  path?: string;
707
746
  }
708
747
 
748
+ /**
749
+ * @h-ai/vecdb — Provider 共享基础层
750
+ *
751
+ * 将各 Provider 的共性逻辑抽取为统一实现:连接守卫、运行时异常捕获、HaiResult 包装。
752
+ *
753
+ * 各 Provider 只需提供 CollectionDriver / VectorDriver(原始操作适配器),
754
+ * base 层统一处理初始化检查与 catch-all 安全网。
755
+ * @module vecdb-provider-base
756
+ */
757
+
758
+ /**
759
+ * 内置后端全部支持文档 ID 扫描。
760
+ * @example 模块内部上下文
761
+ * ```ts
762
+ * function firstPage(vector: BuiltinVectorOperations, name: string) {
763
+ * return vector.scan(name)
764
+ * }
765
+ * ```
766
+ */
767
+ type BuiltinVectorOperations = VectorOperations & Required<Pick<VectorOperations, 'scan'>>;
768
+
709
769
  /**
710
770
  * @h-ai/vecdb — 向量数据库服务主入口
711
771
  *
@@ -739,7 +799,9 @@ interface VecdbConnection {
739
799
  * }
740
800
  * ```
741
801
  */
742
- declare const vecdb: VecdbFunctions;
802
+ declare const vecdb: VecdbFunctions & {
803
+ readonly vector: BuiltinVectorOperations;
804
+ };
743
805
 
744
806
  export { DistanceMetricSchema, HaiVecdbError, VecdbConfigSchema, VecdbOperationLogConfigSchema, vecdb };
745
- export type { ChromaConfig, CollectionCreateOptions, CollectionInfo, CollectionOperations, DistanceMetric, LancedbConfig, PgvectorConfig, QdrantConfig, VecdbConfig, VecdbConfigInput, VecdbConnection, VecdbFunctions, VecdbOperationLogConfig, VecdbType, VectorDocument, VectorOperations, VectorSearchOptions, VectorSearchResult };
807
+ export type { ChromaConfig, CollectionCreateOptions, CollectionInfo, CollectionOperations, DistanceMetric, LancedbConfig, PgvectorConfig, QdrantConfig, VecdbConfig, VecdbConfigInput, VecdbConnection, VecdbFunctions, VecdbOperationLogConfig, VecdbType, VectorDocument, VectorOperations, VectorScanOptions, VectorScanPage, VectorSearchOptions, VectorSearchResult };
package/dist/index.js CHANGED
@@ -34,6 +34,8 @@ var en_US_default = {
34
34
  vecdb_indexBuildFailed: "Failed to build index: {error}",
35
35
  vecdb_serializationFailed: "Serialization/deserialization failed: {error}",
36
36
  vecdb_invalidDimension: "Invalid vector dimension: {dimension}, must be a positive integer",
37
+ vecdb_invalidScanLimit: "The vector scan page size must be an integer between 1 and 128",
38
+ vecdb_invalidScanCursor: "Invalid vector scan cursor. Use the cursor returned by the previous page",
37
39
  vecdb_configPathRequired: "Database storage path is required",
38
40
  vecdb_configDatabaseRequired: "Database name is required",
39
41
  vecdb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
@@ -68,6 +70,8 @@ var ja_JP_default = {
68
70
  vecdb_indexBuildFailed: "\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u69CB\u7BC9\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
69
71
  vecdb_serializationFailed: "\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u307E\u305F\u306F\u30C7\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
70
72
  vecdb_invalidDimension: "\u30D9\u30AF\u30C8\u30EB\u306E\u6B21\u5143\u304C\u7121\u52B9\u3067\u3059: {dimension}\u3002\u6B63\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
73
+ vecdb_invalidScanLimit: "\u30D9\u30AF\u30C8\u30EB\u30B9\u30AD\u30E3\u30F3\u306E\u30DA\u30FC\u30B8\u30B5\u30A4\u30BA\u306B\u306F 1 \u304B\u3089 128 \u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
74
+ vecdb_invalidScanCursor: "\u30D9\u30AF\u30C8\u30EB\u30B9\u30AD\u30E3\u30F3\u306E\u30AB\u30FC\u30BD\u30EB\u304C\u7121\u52B9\u3067\u3059\u3002\u524D\u306E\u30DA\u30FC\u30B8\u304B\u3089\u8FD4\u3055\u308C\u305F\u30AB\u30FC\u30BD\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
71
75
  vecdb_configPathRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u4FDD\u5B58\u30D1\u30B9\u306F\u5FC5\u9808\u3067\u3059",
72
76
  vecdb_configDatabaseRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
73
77
  vecdb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
@@ -102,6 +106,8 @@ var zh_CN_default = {
102
106
  vecdb_indexBuildFailed: "\u6784\u5EFA\u7D22\u5F15\u5931\u8D25: {error}",
103
107
  vecdb_serializationFailed: "\u5E8F\u5217\u5316/\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {error}",
104
108
  vecdb_invalidDimension: "\u65E0\u6548\u7684\u5411\u91CF\u7EF4\u5EA6: {dimension}\uFF0C\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
109
+ vecdb_invalidScanLimit: "\u5411\u91CF\u626B\u63CF\u6BCF\u9875\u6761\u6570\u5FC5\u987B\u4E3A 1 \u5230 128 \u7684\u6574\u6570",
110
+ vecdb_invalidScanCursor: "\u5411\u91CF\u626B\u63CF\u6E38\u6807\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u4E00\u9875\u8FD4\u56DE\u7684\u6E38\u6807",
105
111
  vecdb_configPathRequired: "\u6570\u636E\u5E93\u5B58\u50A8\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A",
106
112
  vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
107
113
  vecdb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
@@ -383,6 +389,18 @@ function createBaseVectorOps(ctx, driver) {
383
389
  HaiVecdbError.QUERY_FAILED,
384
390
  "Failed to count vectors"
385
391
  );
392
+ },
393
+ scan: (collection, options) => {
394
+ const limit = options?.limit ?? 64;
395
+ const cursor = options?.cursor;
396
+ logVecdbOperation(ctx, "read", "vector.scan", { limit, hasCursor: cursor !== void 0 });
397
+ return wrapOp(ctx, async () => {
398
+ if (!Number.isInteger(limit) || limit < 1 || limit > 128)
399
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanLimit"));
400
+ if (cursor !== void 0 && typeof cursor !== "string")
401
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
402
+ return driver.scan(collection, limit, cursor);
403
+ }, HaiVecdbError.QUERY_FAILED, "Failed to scan vector IDs");
386
404
  }
387
405
  };
388
406
  }
@@ -751,6 +769,26 @@ function createChromaProvider() {
751
769
  logger.debug("Counting vectors", { collection });
752
770
  const target = await getCollection(collection);
753
771
  return ok(await target.count());
772
+ },
773
+ async scan(collection, limit, cursor) {
774
+ const offset = cursor === void 0 ? 0 : Number(cursor);
775
+ if (!Number.isSafeInteger(offset) || offset < 0 || cursor !== void 0 && String(offset) !== cursor)
776
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
777
+ try {
778
+ const target = await getCollection(collection);
779
+ const { ids } = await target.get({ offset, limit: limit + 1, include: [] });
780
+ return ok({
781
+ items: ids.slice(0, limit).map((id) => ({ id })),
782
+ nextCursor: ids.length > limit ? String(offset + limit) : void 0
783
+ });
784
+ } catch (error) {
785
+ if (error instanceof Error && ["ChromaNotFoundError", "InvalidCollectionError"].includes(error.name)) {
786
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
787
+ params: { name: collection }
788
+ }));
789
+ }
790
+ throw error;
791
+ }
754
792
  }
755
793
  };
756
794
  return {
@@ -1113,6 +1151,19 @@ function createLancedbProvider() {
1113
1151
  const count = await table.countRows();
1114
1152
  return ok(count);
1115
1153
  });
1154
+ },
1155
+ async scan(collection, limit, cursor) {
1156
+ return withCollection(collection, async (table) => {
1157
+ const query = table.query().select(["id"]).orderBy({ columnName: "id", ascending: true }).limit(limit + 1);
1158
+ if (cursor !== void 0)
1159
+ query.where(`id > '${escapeLanceSqlValue(cursor)}'`);
1160
+ const rows = await query.toArray();
1161
+ const items = rows.slice(0, limit).map((row) => ({ id: row.id }));
1162
+ return ok({
1163
+ items,
1164
+ nextCursor: rows.length > limit ? items.at(-1)?.id : void 0
1165
+ });
1166
+ });
1116
1167
  }
1117
1168
  };
1118
1169
  return {
@@ -1505,6 +1556,25 @@ function createPgvectorProvider() {
1505
1556
  logger3.debug("Counting vectors", { collection });
1506
1557
  const result = await pool.query(`SELECT COUNT(*) as count FROM ${quoteIdent(table)}`);
1507
1558
  return ok(Number.parseInt(String(result.rows[0].count), 10));
1559
+ },
1560
+ async scan(collection, limit, cursor) {
1561
+ const table = quoteIdent(tableName(collection));
1562
+ const request = cursor === void 0 ? pool.query(`SELECT id FROM ${table} ORDER BY id LIMIT $1`, [limit + 1]) : pool.query(`SELECT id FROM ${table} WHERE id > $1 ORDER BY id LIMIT $2`, [cursor, limit + 1]);
1563
+ const page = await request.catch((error) => {
1564
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "42P01")
1565
+ return void 0;
1566
+ throw error;
1567
+ });
1568
+ if (!page) {
1569
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
1570
+ params: { name: collection }
1571
+ }));
1572
+ }
1573
+ const items = page.rows.slice(0, limit).map((row) => ({ id: row.id }));
1574
+ return ok({
1575
+ items,
1576
+ nextCursor: page.rows.length > limit ? items.at(-1)?.id : void 0
1577
+ });
1508
1578
  }
1509
1579
  };
1510
1580
  return {
@@ -1573,6 +1643,21 @@ function createPgvectorProvider() {
1573
1643
  };
1574
1644
  }
1575
1645
  var MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
1646
+ var MAX_QDRANT_POINT_ID = 18446744073709551615n;
1647
+ var scrollIdSchema = z.union([
1648
+ z.guid(),
1649
+ z.bigint().min(0n).max(MAX_QDRANT_POINT_ID)
1650
+ ]);
1651
+ var parseWithSource = JSON.parse;
1652
+ var rawJSON = JSON.rawJSON;
1653
+ function parseScrollJson(text) {
1654
+ return parseWithSource(text, (key, value, context) => {
1655
+ const source = context?.source;
1656
+ if ((key === "id" || key === "next_page_offset") && typeof value === "number" && source && /^\d{1,20}$/.test(source))
1657
+ return BigInt(source);
1658
+ return value;
1659
+ });
1660
+ }
1576
1661
  var pointSchema = z.object({
1577
1662
  id: z.union([
1578
1663
  z.string(),
@@ -1606,7 +1691,7 @@ var QdrantRestClient = class {
1606
1691
  }
1607
1692
  apiKey;
1608
1693
  base;
1609
- async perform(path, method, schema, body) {
1694
+ async perform(path, method, schema, body, parseJson = JSON.parse) {
1610
1695
  const url = new URL(this.base);
1611
1696
  const [pathname, query] = path.split("?");
1612
1697
  url.pathname = `${url.pathname.replace(/\/$/, "")}${pathname}`;
@@ -1652,7 +1737,7 @@ var QdrantRestClient = class {
1652
1737
  }
1653
1738
  let bodyJson;
1654
1739
  try {
1655
- bodyJson = JSON.parse(new TextDecoder().decode(bytes));
1740
+ bodyJson = parseJson(new TextDecoder().decode(bytes));
1656
1741
  } catch {
1657
1742
  throw new Error("Qdrant returned invalid JSON");
1658
1743
  }
@@ -1664,9 +1749,9 @@ var QdrantRestClient = class {
1664
1749
  throw new Error("Qdrant returned an invalid response");
1665
1750
  return envelope.data.result;
1666
1751
  }
1667
- async request(path, method, schema, body) {
1752
+ async request(path, method, schema, body, parseJson) {
1668
1753
  try {
1669
- return await this.perform(path, method, schema, body);
1754
+ return await this.perform(path, method, schema, body, parseJson);
1670
1755
  } catch (error) {
1671
1756
  if (error instanceof QdrantHttpError)
1672
1757
  throw error;
@@ -1709,6 +1794,22 @@ var QdrantRestClient = class {
1709
1794
  }))
1710
1795
  }), options);
1711
1796
  }
1797
+ scroll(name, limit, offset) {
1798
+ return this.request(`/collections/${encodeURIComponent(name)}/points/scroll`, "POST", z.object({
1799
+ points: z.array(z.object({
1800
+ id: scrollIdSchema,
1801
+ payload: z.object({
1802
+ _id: z.unknown().optional()
1803
+ }).nullable().optional()
1804
+ })).max(limit),
1805
+ next_page_offset: scrollIdSchema.nullish()
1806
+ }), {
1807
+ limit,
1808
+ offset: typeof offset === "bigint" ? rawJSON(String(offset)) : offset,
1809
+ with_payload: ["_id"],
1810
+ with_vector: false
1811
+ }, parseScrollJson);
1812
+ }
1712
1813
  count(name, options) {
1713
1814
  return this.request(`/collections/${encodeURIComponent(name)}/points/count`, "POST", z.object({
1714
1815
  count: z.number()
@@ -1962,6 +2063,40 @@ function createQdrantProvider() {
1962
2063
  logger4.debug("Counting vectors", { collection });
1963
2064
  const count = await client.count(collection, { exact: true });
1964
2065
  return ok(count.count);
2066
+ },
2067
+ async scan(collection, limit, cursor) {
2068
+ let offset;
2069
+ if (cursor !== void 0) {
2070
+ if (/^n:(?:0|[1-9]\d{0,19})$/.test(cursor) && BigInt(cursor.slice(2)) <= MAX_QDRANT_POINT_ID)
2071
+ offset = BigInt(cursor.slice(2));
2072
+ else if (/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i.test(cursor))
2073
+ offset = cursor.toLowerCase();
2074
+ else
2075
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
2076
+ }
2077
+ const page = await client.scroll(collection, limit, offset).catch((error) => {
2078
+ if (isQdrantNotFoundError(error))
2079
+ return void 0;
2080
+ throw error;
2081
+ });
2082
+ if (!page) {
2083
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
2084
+ params: { name: collection }
2085
+ }));
2086
+ }
2087
+ const items = page.points.flatMap(({ id, payload }) => {
2088
+ const documentId = payload?._id;
2089
+ return typeof documentId === "string" && hashToUuid(documentId) === id ? [
2090
+ { id: documentId }
2091
+ ] : [];
2092
+ });
2093
+ const next = page.next_page_offset ?? void 0;
2094
+ if (next !== void 0 && next === offset)
2095
+ throw new Error("Qdrant scroll cursor did not advance");
2096
+ return ok({
2097
+ items,
2098
+ nextCursor: typeof next === "bigint" ? `n:${next}` : next
2099
+ });
1965
2100
  }
1966
2101
  };
1967
2102
  return {
@@ -2092,7 +2227,8 @@ var vectorOps = {
2092
2227
  upsert: (collection, documents) => currentProvider && !closingPromise ? currentProvider.vector.upsert(collection, documents) : notInitializedVector.upsert(collection, documents),
2093
2228
  delete: (collection, ids) => currentProvider && !closingPromise ? currentProvider.vector.delete(collection, ids) : notInitializedVector.delete(collection, ids),
2094
2229
  search: (collection, vector, options) => currentProvider && !closingPromise ? currentProvider.vector.search(collection, vector, options) : notInitializedVector.search(collection, vector, options),
2095
- count: (collection) => currentProvider && !closingPromise ? currentProvider.vector.count(collection) : notInitializedVector.count(collection)
2230
+ count: (collection) => currentProvider && !closingPromise ? currentProvider.vector.count(collection) : notInitializedVector.count(collection),
2231
+ scan: (collection, options) => currentProvider && !closingPromise ? currentProvider.vector.scan(collection, options) : notInitializedVector.scan(collection, options)
2096
2232
  };
2097
2233
  var vecdb = {
2098
2234
  /**